Fix Unminified JavaScript for Better LCP

How to minify JavaScript files to reduce payload sizes, improve parse time, and accelerate your Largest Contentful Paint.
Harlan WiltonHarlan Wilton5 min read Published

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 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):

// 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 uses esbuild by default (faster) but Terser produces smaller output.

Webpack:

// webpack.config.js
const TerserPlugin = require('terser-webpack-plugin')

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

Rollup:

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

export default {
  plugins: [
    terser({
      compress: {
        passes: 2,
        drop_console: true,
      },
      mangle: true,
    })
  ]
}

2. Verify Build Output

After configuring, verify your output is actually minified.

ls -la dist/assets/*.js

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

// 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)
function calculateTotal(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.

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

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

For npm packages, ensure you're importing production builds:

// 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 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.

<!-- 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:

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

export default {
  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 manglingcalculateTotalPrice 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.

LCPLargest Contentful Paint CWV
25% weight
Good ≤2.5sPoor >4.0s

Framework-Specific Solutions

Next.jsNext.js minifies automatically in production. Verify with:
npm run build

# + /_app                 87 kB         87 kB
For additional compression, enable SWC minification (default in Next 13+):
// next.config.js
module.exports = {
  swcMinify: true,
}
NuxtNuxt 3 uses Vite with esbuild minification by default. For smaller output with Terser:
// nuxt.config.ts
export default defineNuxtConfig({
  vite: {
    build: {
      minify: 'terser',
    }
  }
})
Check bundle sizes after build:
npx nuxi build
Vue CLIVue CLI minifies in production mode by default:
npm run build -- --mode production
Verify in vue.config.js:
module.exports = {
  productionSourceMap: false, // Smaller builds
  configureWebpack: {
    optimization: {
      minimize: true
    }
  }
}

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:

Original SizeAfter MinificationAfter GzipAfter Brotli
100KB30-40KB10-15KB8-12KB
500KB150-200KB50-70KB40-55KB
1MB300-400KB100-140KB80-110KB

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 it can be cached separately, but ensure it's still minified

Often appears alongside:

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.