A JavaScript bundle exceeding 250KB gzipped takes 2–5 seconds to parse and execute on a mid-range Android device, even on a fast connection — because parse time is CPU-bound, not network-bound. Google's research shows that 100KB of JavaScript takes ~1 second of CPU time on a median mobile device; a 500KB bundle costs 5 seconds before any user interaction is possible. This directly fails the Core Web Vitals Interaction to Next Paint (INP) threshold. CWE-770 (Allocation of Resources Without Limits) applies: shipping an unbounded bundle is a resource management failure that degrades every user's experience proportionally to device age.
Critical because oversized bundles cause multi-second parse delays on mobile devices before any interaction is possible, directly failing Core Web Vitals INP thresholds.
Analyze your production bundle with @next/bundle-analyzer or vite-plugin-visualizer, identify the largest chunks, and apply code splitting or lighter alternatives. Never measure bundle size from a dev server — dev builds include extra runtime overhead.
# Install and run bundle analyzer
npm install --save-dev @next/bundle-analyzer
ANALYZE=true npm run build
// next.config.ts
import bundleAnalyzer from '@next/bundle-analyzer'
const withBundleAnalyzer = bundleAnalyzer({ enabled: process.env.ANALYZE === 'true' })
export default withBundleAnalyzer({})
// Replace static imports of heavy libraries with dynamic imports
// Before
import { BarChart } from 'recharts'
// After
const BarChart = dynamic(() => import('recharts').then(m => m.BarChart), { ssr: false })
For moment/lodash, switch to date-fns and lodash-es which are tree-shakeable, or remove entirely if usage is minimal.
ID: performance-load.bundle.no-large-bundles
Severity: critical
What to look for: Before evaluating, extract and quote the list of all production dependencies from package.json that are actually imported in source files. Examine the production build output (Next.js .next/, SvelteKit build/, etc.) for JavaScript bundle sizes. This means analyzing the output of next build / npm run build (with NODE_ENV=production), not a development server or Turbopack dev build — those include extra runtime overhead and do not reflect real production sizes. List all JS chunks and their sizes. Check for any single bundle over 250KB gzipped. This includes the main app bundle and route chunks. If a production build is not available or accessible, analyze the source code for patterns that would produce large bundles: barrel files that import entire libraries, large client-side libraries (charting, editor, PDF) imported without tree-shaking, or missing dynamic imports for heavy components. Note in the detail field that analysis was based on source patterns rather than an actual production build.
Pass criteria: All JavaScript bundles in the production build are under 250KB gzipped. The main application bundle is typically 100-200KB gzipped. No more than 0 chunks should exceed 250KB gzipped. If no production build is available but source analysis shows no patterns that would produce large bundles, this may be passed with a note. Report even on pass: "Largest chunk is XKB gzipped out of Y total chunks."
Fail criteria: Any single JavaScript bundle exceeds 250KB gzipped in the production build, indicating missing code splitting or bundled dependencies. Or source analysis reveals patterns (large un-tree-shaken imports, missing dynamic imports for heavy libraries) that would predictably produce oversized bundles.
Skip (N/A) when: Never — bundle size is fundamental to web performance.
Important — installed vs. imported: When evaluating source patterns without a production build, look only at libraries that are actually imported in source files, not merely listed in package.json. Unused dependencies do not affect bundle size (the no-unused-dependencies check covers installed-but-unused packages separately). Evaluate based on: (1) imports of known-large libraries (moment, lodash full bundle, etc.) in source files, (2) barrel imports that pull entire libraries (import { x } from 'lodash' vs. import x from 'lodash/x'), and (3) client-side imports of server-only packages.
Detail on fail: "Main app bundle is 450KB gzipped — exceeds 250KB threshold. Largest 3 chunks: main.js (450KB), dashboard.js (320KB), vendor.js (280KB)" or "No production build available; source imports recharts (180KB) and monaco-editor (2MB) without dynamic import — likely exceeds 250KB threshold"
Remediation: Reduce bundle size through code splitting and dependency analysis:
# Analyze bundle size
npm install --save-dev @next/bundle-analyzer
# OR
npm install --save-dev vite-plugin-visualizer
# Check individual bundle sizes
npm run build -- --analyze
// next.config.ts — enable analysis
import bundleAnalyzer from '@next/bundle-analyzer'
const withBundleAnalyzer = bundleAnalyzer({
enabled: process.env.ANALYZE === 'true',
})
export default withBundleAnalyzer({...})
Then investigate and remove unnecessary dependencies:
// Before — importing heavy library everywhere
import { momentjs } from 'moment'
// After — lazy load if not needed immediately
const moment = dynamic(() => import('moment'), { ssr: false })