---
title: "Fix Unminified JavaScript for Better LCP"
description: "How to minify JavaScript files to reduce payload sizes, improve parse time, and accelerate your Largest Contentful Paint."
canonical_url: "https://unlighthouse.dev/learn-lighthouse/lcp/unminified-javascript"
last_updated: "2025-01-18"
---

Unminified JavaScript files can be 60-80% larger than necessary. Lighthouse flags scripts where over 10% of bytes could be saved through minification - wasted bytes that directly delay your LCP.

## What's the problem?

Minification removes whitespace, shortens variable names, and eliminates dead code without changing functionality. When you ship unminified JavaScript, you're transferring unnecessary bytes that serve no purpose in production.

Lighthouse triggers this audit when a script has more than 10% potential savings AND at least 2KB could be saved. That threshold exists because minification always provides some benefit - only scripts with significant waste get flagged.

**Why it hurts LCP:** JavaScript must download and parse before your page becomes interactive. Unminified scripts take longer to download (more bytes) and longer to parse (more tokens for the engine to process). If your LCP element depends on JavaScript to render, common in SPAs, those extra milliseconds directly delay your largest paint.

Consider a typical [React](https://react.dev) app bundle. An unminified version might be 800KB. Minified with Terser, it drops to 250KB. With gzip compression on top, it becomes 80KB. That's a 10x reduction in transfer size from two simple build steps.

## How to identify this issue

### Lighthouse

Look for the audit "Minify JavaScript" under Opportunities. It shows:

- Each unminified script URL
- Total transfer size
- Potential savings in KB
- Red flag if savings exceed 8KB per file

### Chrome DevTools

**Network Tab Method:**

1. Open DevTools → Network tab
2. Filter by "JS" type
3. Click a script file
4. Look at the Response tab - readable code with whitespace indicates unminified

**Sources Tab Method:**

1. Open DevTools → Sources tab
2. Navigate to your JS files
3. If code is readable with full variable names and comments, it's unminified
4. Minified code appears as long, dense lines with single-letter variables

### Coverage tab

1. Open DevTools → More tools → Coverage
2. Click the reload button
3. Red bars indicate unused code, but check file names - `.min.js` suggests minification, plain `.js` may not be

## The fix

### 1. configure your bundler for production mode

Most bundlers minify automatically in production mode.

**Vite (recommended):**

```js
// vite.config.js
import { defineConfig } from 'vite'

export default defineConfig({
  build: {
    minify: 'terser', // or 'esbuild' (default, faster)
    terserOptions: {
      compress: {
        drop_console: true, // Remove console.log
        drop_debugger: true // Remove debugger statements
      }
    }
  }
})
```

[Vite](https://vite.dev) uses [esbuild](https://esbuild.github.io) by default (faster) but Terser produces smaller output.

**webpack:**

```js
// webpack.config.js
import TerserPlugin from 'terser-webpack-plugin'

export default {
  mode: 'production', // Enables minification
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          compress: {
            drop_console: true,
          },
          mangle: true,
          format: {
            comments: false,
          },
        },
        extractComments: false,
      }),
    ],
  },
}
```

**Rollup:**

```js
// rollup.config.js
import terser from '@rollup/plugin-terser'

const config2 = {
  plugins: [
    terser({
      compress: {
        passes: 2,
        drop_console: true,
      },
      mangle: true,
    })
  ]
}
```

### 2. verify build output

After configuring, verify your output is minified.

```bash
ls -la dist/assets/*.js
```

Inspect the output file - it should be dense, single-line code:

```js
// Unminified (don't ship this)
function calculateTotal(items) {
  let total = 0
  for (const item of items) {
    total += item.price * item.quantity
  }
  return total
}
```

// Minified (ship this)

```text
function calculateTotalMinified(e){let t=0;for(const l of e)t+=l.price*l.quantity;return t}
```

### 3. minify third-party scripts

Self-hosted libraries might not be minified. Check and replace.

```html
<!-- Before: unminified -->
<script src="/js/lodash.js"></script>

<!-- After: minified -->
<script src="/js/lodash.min.js"></script>
```

For [npm](https://npmjs.com) packages, ensure you're importing production builds:

```js
// Some packages require explicit production imports
import Vue from 'vue/dist/vue.runtime.min.js'
```

### 4. use a CDN with automatic minification

CDNs like [Cloudflare](https://cloudflare.com) can minify JavaScript on the fly.

**Cloudflare settings:**

1. Speed → Optimization → Auto Minify
2. Enable "JavaScript" checkbox

This catches any scripts that slip through your build process.

### 5. minify inline scripts

Don't forget `<script>` tags embedded in HTML.

```html
<!-- Before -->
<script>
  // Initialize analytics
  window.dataLayer = window.dataLayer || [];
  function gtag() {
    dataLayer.push(arguments);
  }
  gtag('js', new Date());
  gtag('config', 'G-XXXXXX');
</script>

<!-- After -->
<script>window.dataLayer=window.dataLayer || [];function gtag(){dataLayer.push(arguments)}gtag("js",new Date);gtag("config","G-XXXXXX");</script>
```

Use HTML minification plugins to handle this automatically:

```js
// vite.config.js
import { createHtmlPlugin } from 'vite-plugin-html'

const config3 = {
  plugins: [
    createHtmlPlugin({
      minify: true
    })
  ]
}
```

## Why this works

Minification reduces JavaScript size through three mechanisms:

1. **Whitespace removal**: Spaces, tabs, and newlines add zero value in production
2. **Identifier mangling**: `calculateTotalPrice` becomes `a`, `userPreferences` becomes `b`
3. **Dead code elimination**: Unused exports and unreachable branches are stripped

The result: 60-80% smaller files that parse faster. Smaller files also compress better - Brotli works more efficiently on dense, repetitive minified code.

<audit-impact current-value="3.8s" metric="lcp" target-value="2.9s">

Minifying 800KB of JavaScript to 250KB on a single-page application.

</audit-impact>

## Framework-specific solutions

<callout icon="i-logos-nextjs-icon">

**Next.js**

[Next.js](https://nextjs.org) minifies automatically in production. Verify with:

```bash
npm run build

# + /_app 87 kb 87 kb
```

For additional compression, enable SWC minification (default in Next 13+):

```js
// next.config.js
const config4 = {
  swcMinify: true,
}
```

</callout>

<callout icon="i-logos-nuxt-icon">

**Nuxt**

Nuxt 3 uses Vite with esbuild minification by default. For smaller output with Terser:

```ts
// nuxt.config.ts
export default defineNuxtConfig({
  vite: {
    build: {
      minify: 'terser',
    }
  }
})
```

Check bundle sizes after build:

```bash
npx nuxi build
```

</callout>

<callout icon="i-logos-vue">

**Vue CLI**

Vue CLI minifies in production mode by default:

```bash
npm run build -- --mode production
```

Verify in `vue.config.js`:

```js
const config5 = {
  productionSourceMap: false, // Smaller builds
  configureWebpack: {
    optimization: {
      minimize: true
    }
  }
}
```

</callout>

## Verify the fix

After implementing:

1. Run Lighthouse → "Minify JavaScript" should pass or show minimal savings
2. Check DevTools Sources → JS files should be dense, unreadable code
3. Compare file sizes before/after (aim for 60-80% reduction)
4. Test in staging to ensure functionality isn't broken

**File size targets:**

<table>
<thead>
  <tr>
    <th>
      Original Size
    </th>
    
    <th>
      After Minification
    </th>
    
    <th>
      After Gzip
    </th>
    
    <th>
      After Brotli
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      100KB
    </td>
    
    <td>
      30-40KB
    </td>
    
    <td>
      10-15KB
    </td>
    
    <td>
      8-12KB
    </td>
  </tr>
  
  <tr>
    <td>
      500KB
    </td>
    
    <td>
      150-200KB
    </td>
    
    <td>
      50-70KB
    </td>
    
    <td>
      40-55KB
    </td>
  </tr>
  
  <tr>
    <td>
      1MB
    </td>
    
    <td>
      300-400KB
    </td>
    
    <td>
      100-140KB
    </td>
    
    <td>
      80-110KB
    </td>
  </tr>
</tbody>
</table>

## Common mistakes

- **Forgetting to set NODE_ENV=production**: Many tools only minify when `process.env.NODE_ENV === 'production'`
- **Shipping source maps to production**: Source maps help debugging but add significant bytes. Disable or host separately.
- **Double-minifying**: Minifying already-minified code wastes build time and can break code
- **Aggressive compression breaking code**: Some Terser options (like `unsafe`) can break functionality. Test thoroughly.
- **Ignoring vendor bundles**: Split vendor code so the browser can cache it separately, but make sure you still minify it.

## Related issues

Often appears alongside:

- [Unused JavaScript](/learn-lighthouse/lcp/unused-javascript) - Even minified unused code wastes bytes
- [Total Byte Weight](/learn-lighthouse/lcp/total-byte-weight) - Unminified JS inflates total page weight
- [Render-Blocking Resources](/learn-lighthouse/lcp/render-blocking-resources) - Large JS files block rendering longer

## Test your entire site

Different pages may load different JavaScript bundles - some minified, some not. [Unlighthouse](/) scans every page on your site and flags any scripts that aren't properly minified.
