Code Splitting & Lazy Loading
Optimize your application's "Time to Interactive" (TTI) by shipping only the code your users need. Master dynamic imports, route-based splitting, and the modern bundling strategies required for production-grade performance.
The Power of Dynamic Imports
Standard `import` statements are static—they are resolved at compile time and included in the main bundle. Dynamic `import()` returns a Promise, allowing you to request a JavaScript module at runtime based on user actions or environment conditions.
// Architectural Logic: Dynamic Imports
// Loading a heavy module only when the interaction occurs
const initializeEditor = async () => {
// 1. Module is requested from the server on demand
const { RichEditor } = await import('./RichEditor.js');
// 2. Instantiate and mount
const editor = new RichEditor();
editor.render('#editor-root');
};
document.getElementById('edit-btn').addEventListener('click', initializeEditor);Route-Based Architectural Splitting
The most common performance win in modern SPAs is route splitting. By wrapping page components in dynamic imports, you ensure that a user visiting your landing page doesn't download the source code for the complex analytics dashboard they haven't accessed yet.
// Engineering Pattern: Route-Based Splitting
// Loading only the necessary code for the current URL view
const routes = {
'/dashboard': () => import('./views/Dashboard.js'),
'/settings': () => import('./views/Settings.js')
};
async function handleNavigation(path) {
const loader = routes[path];
if (loader) {
// High-level modules are separated into individual JS chunks
const { default: Component } = await loader();
renderView(Component);
}
}Bundling Strategy: The HTTP/2 Shift
In the era of HTTP/1.1, browsers had a strict limit of 6-8 concurrent connections per domain, making "mega-bundles" necessary. **HTTP/2 Multiplexing** changed this, allowing many files to stream over a single connection.
// Historical Context: HTTP/1.1 vs HTTP/2
// HTTP/1.1 (Waterfall): Limited concurrent requests. Thick bundles were faster.
// HTTP/2 (Multiplexing): Hundreds of concurrent requests. Small individual chunks are faster.
// Recommendation for Modern Engineering:
// Break bundles into smaller, specific chunks.
// This allows for better caching granularity and faster initial interaction.Technical Insight: Resource Hints
Combine code splitting with `rel="preload"` or `rel="prefetch"`. **Preload** is for resources needed for the current page, while **Prefetch** is for low-priority chunks the user might need in the *next* navigation cycle.
Code Splitting Checklist:
- ✅ **Entry Points:** Audit your main bundle size using tools like `webpack-bundle-analyzer`.
- ✅ **Modals:** Move heavy modals and sidebars to lazy-loaded chunks.
- ✅ **Libraries:** Only import specific functions from heavy libraries (e.g., `lodash-es/debounce`).
- ✅ **Fallback UI:** Always provide a loading state (Skeleton or Spinner) during the chunk fetch.
- ✅ **Error Boundaries:** Handle network failures during chunk loading using `try/catch`.
- ✅ **Granularity:** Avoid "Over-Splitting"—too many tiny chunks can increase management overhead.