Code Splitting and Lazy Loading Optimization#

A fundamental question for optimizing web app performance in interviews is:

What is code splitting, how does it work under the hood, and how do you implement it in modern React/Next.js applications?

To answer this, you must cover the mechanics of modular bundlers, dynamic imports, and component lazy loading.


1. What Is Code Splitting?#

By default, bundlers (like Webpack, Vite, or Turbopack) merge all imported module code into a single, large JavaScript bundle.

Code Splitting is the practice of splitting this single bundle into multiple smaller files (chunks) that can be loaded on-demand. This reduces the initial JavaScript bundle size, improving loading metrics like Time to Interactive (TTI) and Total Blocking Time (TBT).


2. Dynamic Imports: The Foundation#

Under the hood, code splitting is triggered by the ES6 dynamic import() syntax:

JAVASCRIPT
// Static import (bundled together)
import { calculateStats } from "./analytics";

// Dynamic import (split into a separate chunk)
import("./analytics").then((module) => {
  module.calculateStats();
});

When a bundler encounters a dynamic import(), it automatically creates a new split point and compiles that file (and its private dependencies) into a separate .js file. In the browser, calling import() triggers a dynamic network fetch (JSONP or ES Modules fetch) to download the chunk on-demand.


3. Code Splitting in React#

React applications implement code splitting using React.lazy and Suspense:

JSX
import React, { Suspense, lazy } from "react";

// Lazy load the component
const HeavyChart = lazy(() => import("./components/HeavyChart"));

export default function Dashboard() {
  return (
    <div>
      <h1>User Dashboard</h1>
      <Suspense fallback={<div>Loading chart...</div>}>
        <HeavyChart />
      </Suspense>
    </div>
  );
}

4. Code Splitting in Next.js#

In Next.js (App Router), code splitting is handled automatically at two levels:

  1. Route-Level: Every page inside the app/ directory is automatically code-split. Navigating to /about only downloads the JS bundle required for /about, not the entire site.
  2. Component-Level: Use Next.js's next/dynamic utility (which wraps React.lazy):
    TYPESCRIPT
    import dynamic from 'next/dynamic';
    const Modal = dynamic(() => import('../components/Modal'), {
      ssr: false, // disable server rendering if it is a client-only modal
    });
    

Key Takeaways#

  • Code Splitting: Reduces initial bundle size by creating split-point chunks.
  • Dynamic Imports: Relies on dynamic import() which compiles files into separate chunks and fetches them asynchronously.
  • Splitting Levels: Route-level splitting is automatic in Next.js, while component-level splitting can be done via next/dynamic.