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:
// 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:
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:
- Route-Level: Every page inside the
app/directory is automatically code-split. Navigating to/aboutonly downloads the JS bundle required for/about, not the entire site. - Component-Level: Use Next.js's
next/dynamicutility (which wrapsReact.lazy):TYPESCRIPTimport 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.