Bỏ qua để vào nội dung chính
Bundle Size Audit: How I Reduced My Next.js Bundle by 60%

Bundle Size Audit: How I Reduced My Next.js Bundle by 60%

Bởi Benjamin Hayes
24 thg 3, 20267 phút đọc

A step-by-step walkthrough of auditing and shrinking a bloated Next.js bundle — from 420KB to 168KB gzipped — using real tools and techniques.

Last month I ran next build and stared at the output. Our main bundle was 420KB gzipped. For a content site with a blog and some interactive components, that was absurd. Four weeks later, it was down to 168KB. Here's every step I took.

Step 1: Visualize the Damage

Before cutting anything, you need to see what's actually in your bundle. Install @next/bundle-analyzer and add it to your Next.js config:

// next.config.ts
import withBundleAnalyzer from "@next/bundle-analyzer";

const config = withBundleAnalyzer({
  enabled: process.env.ANALYZE === "true",
})({
  // your existing config
});

export default config;

Run ANALYZE=true next build and open the generated HTML report. The treemap visualization immediately reveals the biggest offenders. In my case: moment.js (67KB), lodash (full bundle at 71KB), an icon library shipping 4000 icons when we used 12, and a markdown parser pulled in by a component we no longer used.

Step 2: Replace Heavy Dependencies

The easiest wins come from swapping bloated libraries for lighter alternatives. Here's what I replaced:

  • moment.js → date-fns: Tree-shakeable, only imports the functions you use. Saved ~60KB.
  • lodash → native JS + lodash-es: Most lodash utilities have native equivalents. For the few I kept (debounce, groupBy), switching to lodash-es enabled tree shaking. Saved ~65KB.
  • react-icons → lucide-react: Lucide ships individual ESM exports. Each icon is ~1KB instead of pulling the entire icon set.

Step 3: Dynamic Imports for Below-the-Fold

Components that aren't visible on initial load shouldn't be in the main bundle. Next.js makes this trivial with next/dynamic:

import dynamic from "next/dynamic";

// Heavy chart component — only loads when scrolled into view
const AnalyticsChart = dynamic(
  () => import("@/components/analytics-chart"),
  {
    loading: () => <div className="h-64 animate-pulse bg-muted rounded" />,
    ssr: false,
  }
);

// Rich text editor — only needed on write page
const TipTapEditor = dynamic(
  () => import("@/components/editor/tiptap-editor"),
  { ssr: false }
);

This moved about 90KB of JavaScript out of the critical path. The components load on demand when the user actually needs them.

Step 4: Audit Your Barrel Files

Barrel files (index.ts that re-export everything) are tree-shaking killers. If you import one function from a barrel file, the bundler may pull in everything. I restructured our component exports to use direct imports:

- import { Button, Card, Dialog } from "@/components/ui";
+ import { Button } from "@/components/ui/button";
+ import { Card } from "@/components/ui/card";
+ import { Dialog } from "@/components/ui/dialog";

Step 5: Check for Duplicate Dependencies

Run npm ls <package-name> to check for duplicate versions of the same library. I found two versions of zod and three versions of react-dom (from incompatible peer deps). Aligning versions and using npm dedupe saved another 25KB.

Step 6: Leverage Server Components

The biggest architectural win was moving data-fetching components to React Server Components. Server Components send zero JavaScript to the client. Our category sidebar, footer with dynamic links, and article metadata sections all became server-rendered, removing their entire JavaScript cost from the client bundle.

Results

  • Before: 420KB gzipped main bundle, 2.1s TTI on 3G
  • After: 168KB gzipped, 0.9s TTI on 3G
  • Lighthouse Performance: 62 → 94

Takeaways

  • Always visualize before optimizing — @next/bundle-analyzer is non-negotiable
  • Replace moment.js and full lodash — there's no excuse in 2026
  • Dynamic import everything below the fold
  • Direct imports over barrel files for UI component libraries
  • Server Components are free performance — use them aggressively

Không spam, hủy đăng ký bất kỳ lúc nào.

Bài viết liên quan