Your Bundle Is 4000x Bigger Than Quake: The 9-Step Audit That Fixes It

In February 2026, developer daivuk shipped a playable Quake-like first-person shooter in a 64 kilobyte Windows executable. Multiple levels, four enemy types, textures, music — the whole game. The trick? He wrote a custom language and virtual machine because the standard toolchain shipped features he didn't use. Two extra kilobytes of generic runtime would have killed the fourth level.

Your web app is 4000 times bigger. The page you're reading on Dev.to weighs more than 400 copies of that game running at once. We've forgotten what bytes cost.

This is the audit playbook I use when a Next.js or Vite project shows orange Lighthouse scores. Nine steps, exact commands, expected output, typical wins. Cut your bundle 50-90% in a single afternoon. No "rewrite in Rust" theater. Just deletions.

Step 1: Baseline

Run npx next build and read the "First Load JS" table. Write down the shared number and largest route. The HTTP Archive 2025 annual web almanac reports median JS transfer size of 612 KB on desktop, 555 KB on mobile. If you're above that, you have low-hanging fruit.

Step 2: Visualize the Bundle

Install @next/bundle-analyzer or rollup-plugin-visualizer. Run the build with ANALYZE=true. A treemap opens in your browser. Every fat block is a question. Spend 10 minutes hovering rectangles — the ones you can't explain are the biggest byte drains.

Step 3: Kill the Date Library

Moment.js is 67 KB minified before gzip. day.js is 7 KB. date-fns with tree shaking drops to 12 KB. Native Intl.DateTimeFormat is zero bytes. Run a global grep for moment and dayjs. Replace with native or tree-shaken alternatives. Typical win: 50-90 KB.

// before
import moment from 'moment'
const formatted = moment(date).format('YYYY-MM-DD')

// after
import { format } from 'date-fns'
const formatted = format(date, 'yyyy-MM-dd')

Step 4: Kill the Icon Set Import

Default barrel imports ship entire icon sets (Material UI icons = ~2 MB of SVG). Use per-icon imports instead:

// before — ships entire icon set
import { Search, User, Menu } from '@mui/icons-material'

// after — ships only three icons
import Search from '@mui/icons-material/Search'
import User from '@mui/icons-material/Person'
import Menu from '@mui/icons-material/Menu'

Check your analyzer treemap for the full icon library. If it's there, tree-shaking didn't happen. Fix import paths. Typical win: 20-200 KB.

Step 5: Kill Lodash

Lodash is 70 KB. Most apps use 7 functions. Replace with native equivalents or lodash-es with tree shaking:

// before
import _ from 'lodash'
const grouped = _.groupBy(items, 'category')
const unique = _.uniq(ids)

// after
const grouped = Object.groupBy(items, item => item.category)
const unique = [...new Set(ids)]

Object.groupBy shipped in 2024. Map.groupBy is also available. Typical win: 60-80 KB.

Step 6: Audit Polyfills

If you target browsers older than last 2 versions of Chrome, Safari, Firefox, you're shipping unnecessary polyfills. Update browserslist in package.json:

"browserslist": ["last 2 chrome versions", "last 2 firefox versions", "last 2 safari versions", "last 2 edge versions"]

Check analyzer for core-js, regenerator-runtime, @babel/runtime. Each shrinks when you raise target. Typical win: 30-100 KB (unless you need IE 11).

Step 7: Code-Split by Route

Dynamic imports for non-critical paths:

import { lazy, Suspense } from 'react'
const HeavyDashboard = lazy(() => import('./HeavyDashboard'))

In Next.js App Router, routes split automatically. Split heavy components inside a route — chart libraries, markdown editors, video players, payment SDKs. Typical win: 100 KB to 1 MB.

Step 8: Replace Images

Serve AVIF with WebP fallback, sized to display dimensions. Use `` element:


  
  
  

AVIF shaves 30-50% off JPEG at same quality. Typical win: 200 KB to 2 MB.

Step 9: Re-Baseline

Run step 1 again. Write down new number. On a typical Next.js app with heavy dashboard, icon library, lodash, and unoptimized images, First Load JS drops from 400-600 KB to 100-200 KB. Lighthouse score jumps 20-40 points.

Keep the Wins

Three rules for CI:

  1. Bundle budget with bundlewatch — fail the build on regression.
  2. Dependency review on every PR touching package.json — post analyzer diff.
  3. Monthly "what got fat" report — run analyzer, fix the biggest rectangle.

Without these rules, wins drift back in 6 months.

The lesson from QUOD isn't about 64 KB — it's about constraint. Every dependency is a vote against users on slow connections. Every imported icon set taxes battery on flights. The audit pays back in hours, not weeks. I cut 540 KB to 168 KB in one afternoon. Do it in one focused sweep.

Next time you reach for a 4 MB library to format a date, think about QUOD. Then think whether your users would rather download your app or 400 copies of a game with guns.