Optimizing Bundle Delivery with Rollup Code Splitting
Learn how to use Rollup's code splitting and dynamic imports to reduce redundant downloads and optimize browser caching for multi-entry applications.
25 Mar 2026, 11:40 UTC

The Problem: Redundant Downloads and Bloated Bundles
When building a multi-page application or a complex dashboard, you often find the same heavy libraries—like Lodash, React, or D3—imported across multiple entry points. If you bundle each page as a standalone file, the user downloads the same dependency every time they navigate to a new section. Conversely, creating one massive "vendor.js" file forces users to download code they may never actually use.
The goal is a middle ground: automatic code splitting. By using dynamic import() and Rollup's graph analysis, you can ensure shared code is extracted into a single chunk that the browser downloads once and caches for the duration of the session.
How Rollup Handles Shared Dependencies
Rollup treats your code as a dependency graph. When it encounters multiple entry points or dynamic import expressions that reference the same module, it identifies that module as a "shared chunk." Instead of duplicating the code in every output file, Rollup emits a separate file for the shared logic and inserts import statements in the entry files to fetch it.
This is superior to manual vendor bundling because it is reactive. If you remove a dependency from one page, Rollup automatically adjusts the shared chunk boundaries. You don't have to maintain a manual list of libraries to be "externalized" or grouped.
Implementing Dynamic Splitting
To trigger code splitting, you must use the dynamic import() syntax. This tells Rollup that the module is not needed immediately at startup and can be loaded asynchronously.
Worked Example: Shared Utility Logic
Imagine an application with a main user interface (app.js) and an administrative panel (admin.js), both of which rely on a heavy utility library.
// src/app.js
import { formatData } from './utils.js';
console.log('App loaded:', formatData('User Data'));
// src/admin.js
import { formatData } from './utils.js';
console.log('Admin loaded:', formatData('Admin Data'));
// src/utils.js
export const formatData = (data) => `Formatted: ${data}`;
To output these as split chunks, use a configuration that specifies a directory for output rather than a single file, and set the format to es (ES Modules), as code splitting requires a module system that supports asynchronous loading.
// rollup.config.js
export default {
input: ['src/app.js', 'src/admin.js'],
output: {
dir: 'dist',
format: 'es',
chunkFileNames: 'chunks/[name]-[hash].js',
entryFileNames: '[name].js'
}
};
Execution: Run this via the CLI (e.g., npx rollup -c) with appropriate project permissions. Rollup will generate dist/app.js, dist/admin.js, and a shared chunk in dist/chunks/ containing the utils.js logic.
Fine-Tuning with manualChunks
While automatic splitting is efficient, you may want to force specific libraries into their own chunks to improve long-term caching. For example, your application code changes daily, but your dependencies change monthly. By isolating dependencies, the user's browser can keep the vendor chunk cached even after you deploy a UI update.
You can use the manualChunks option to group modules:
output: {
dir: 'dist',
format: 'es',
manualChunks: {
'vendor': ['lodash', 'axios']
}
}
This forces lodash and axios into a file named vendor-[hash].js regardless of where they are imported in the graph.
Critical Limitations and Trade-offs
Code splitting is not a "silver bullet" and introduces specific technical constraints:
- Format Constraints: Code splitting only works with
es(ES Modules) orsystemjs. If you set the output format toiife(Immediately Invoked Function Expression), Rollup will throw an error or attempt to inline the dynamic imports, defeating the purpose of splitting. - Request Overhead: Splitting creates more HTTP requests. On HTTP/1.1 connections, this can lead to "head-of-line blocking," potentially slowing down the page load. This is significantly mitigated by HTTP/2 or HTTP/3.
- Execution Order: Using
manualChunkscan occasionally lead to circular dependency issues if you split modules that have side effects (code that runs immediately upon import). Always verify the load order in the browser network tab.
Verification Checklist
To verify your splitting strategy is working:
- Inspect the
distfolder: Ensure there are more files than entry points. - Browser Network Panel: Load
app.js, then navigate toadmin.js. The shared chunk should be loaded from the disk cache (Status 200 or 304) rather than downloaded again. - Check for Inlining: Search the entry files for the shared code. If the actual logic of the utility library appears inside
app.js, yourformatorinputconfiguration is likely incorrect.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.