Optimize Initial Payload with Rollup Dynamic Imports and manualChunks
Reduce initial bundle size by using Rollup's dynamic import() and manualChunks to isolate vendor libraries and lazy-load feature modules on demand.
17 Aug 2026, 05:36 UTC

When Rollup bundles all static imports into a single entry file, the initial payload grows linearly with the project size, delaying the time to interactive. To solve this, you must define explicit split points using dynamic import() and use the manualChunks configuration to isolate third-party dependencies from application logic.
Desired Outcome
A production build consisting of a lightweight entry.js (containing only bootstrap logic), a dedicated vendor chunk for stable libraries, and separate lazy-loaded chunks for specific features or routes that load only upon user interaction.
Prerequisites
- A Rollup project with
@rollup/plugin-node-resolveand@rollup/plugin-commonjsinstalled and configured. - Source code utilizing ESM (ECMAScript Modules).
- A target environment that supports dynamic imports or a compatible polyfill for older browsers.
- Node.js environment with write permissions to the output directory (e.g.,
dist/).
Implementation Procedure
1. Define Split Points in Source Code
Rollup identifies code-splitting boundaries via the import() function. Replace static imports with dynamic imports for heavy features or routes.
// src/main.js
import { initApp } from './core.js';
initApp();
// This creates a split point; Rollup will move this module to a separate chunk
document.getElementById('settings-btn').addEventListener('click', async () => {
const { openSettings } = await import('./features/settings.js');
openSettings();
});
2. Configure Manual Chunking and Naming
To prevent vendor libraries from being duplicated across multiple feature chunks, use the manualChunks option in rollup.config.js. This allows you to group specific modules into named chunks regardless of where they are imported.
// rollup.config.js
export default {
input: 'src/main.js',
output: {
dir: 'dist',
format: 'esm',
entryFileNames: 'entry.js',
chunkFileNames: 'chunks/[name]-[hash].js',
manualChunks(id) {
// Group all node_modules into a single vendor chunk
if (id.includes('node_modules')) {
return 'vendor';
}
// Group specific feature directories into their own chunks
if (id.includes('/src/features/settings')) {
return 'feature-settings';
}
if (id.includes('/src/features/profile')) {
return 'feature-profile';
}
}
},
plugins: [/* resolve, commonjs */]
};
3. Manage Module Side Effects
Aggressive tree-shaking may remove modules that perform global setup (like polyfills or CSS imports) if they aren't explicitly called. Mark these in package.json to ensure they are preserved during the splitting process.
// package.json
{
"sideEffects": [
"./src/polyfills.js",
"**/*.css"
]
}
Verification and Checks
- Build Inspection: Run
rollup -c. Verify thedist/folder containsentry.jsand achunks/directory containingvendor-[hash].jsand the named feature chunks. - Network Analysis: Load the application in a browser. Open the Network tab in DevTools. Confirm that
feature-settings-[hash].jsis only requested after clicking the settings button, not during the initial page load. - Dependency Audit: Use a bundle analyzer (e.g.,
rollup-plugin-visualizer) to ensure that large libraries (like React or Lodash) reside exclusively in thevendorchunk and are not duplicated inside feature chunks.
Limitations: Manual chunking can occasionally increase the total byte count if shared code is forced into a chunk that is rarely used. Dynamic imports require a modern browser or a runtime loader. If preserveModules is set to true (common in library builds), manualChunks may be ignored.
Recovery Options
If the build fails or runtime errors occur after splitting:
- Revert to Default Splitting: Remove the
manualChunksfunction. Rollup will still split atimport()boundaries, but will assign automatic IDs to the chunks. - Restore Static Imports: Replace
await import()with a standardimport ... from ...statement to move the problematic module back into the main entry bundle. - Force Side Effect Preservation: If a module's logic disappears, set
treeshake: { moduleSideEffects: true }in the Rollup config to disable the removal of side-effectful code.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.