Master Long-Term Caching and Code Splitting to Supercharge Your Webpack Builds
This article explains how to improve web application performance by leveraging persistent caching with proper cache‑control headers, versioning bundles using hash‑based filenames, extracting dependencies and runtime into separate chunks, inlining runtime scripts, applying lazy loading with dynamic imports, splitting code by routes or pages, and stabilizing module IDs with hashed IDs, all using webpack configurations for both version 3 and 4.
Utilize Persistent Caching
After optimizing application size, the next strategy to improve load time is caching. Storing resources on the client avoids re‑downloading them on subsequent visits.
Bundle Versioning and Cache Headers
Common caching approach:
Tell the browser to cache a file for a long period (e.g., one year):
# Server header
Cache-Control: max-age=31536000Note: If you are unfamiliar with the Cache-Control mechanism, refer to Jake Archibald’s article on caching best practices.
When a file changes, rename it to force the browser to download the new version:
<!-- before -->
<script src="./index-v15.js"></script>
<!-- after -->
<script src="./index-v16.js"></script>This tells the browser to download the JS file once and reuse the cached copy until the filename changes or the cache expires.
With webpack you can achieve the same effect using the [chunkhash] placeholder in the filename:
// webpack.config.js
module.exports = {
entry: './index.js',
output: {
filename: 'bundle.<strong>[chunkhash]</strong>.js',
// → bundle.8e0d62a03.js
}
};⭐️ Note: Even if the bundle content does not change, webpack may generate a different hash (e.g., after renaming a file or compiling on a different OS). This is a known issue without a clear solution yet.
If you need to expose the generated filename to the client, you can use HtmlWebpackPlugin or WebpackManifestPlugin:
// index.html
<script src="bundle.8e0d62a03.js"></script>Extract Dependencies and Runtime into Separate Files
Dependencies
Application dependencies change less frequently than application code. Moving them to a separate file allows the browser to cache them independently, so code changes do not force a re‑download of the libraries.
Key term: In webpack terminology, an independent file that contains application code is called a chunk .
Steps to extract dependencies:
Replace the output filename with [name].[chunkname].js:
// webpack.config.js
module.exports = {
output: {
// Before
filename: 'bundle.[chunkhash].js',
// After
filename: '[name].[chunkhash].js'
}
};When webpack compiles, it uses [name] as the chunk name. Without [name] , you would have to rely on the hash, which is harder to manage.
Change entry to an object so each entry gets a name:
// webpack.config.js
module.exports = {
// Before
entry: './index.js',
// After
entry: {
main: './index.js'
}
};The name "main" becomes the [name] placeholder.
In webpack 4 you can enable automatic extraction with:
// webpack.config.js (webpack 4)
module.exports = {
optimization: {
splitChunks: {
chunks: 'all'
}
}
};In webpack 3 you achieve the same effect with CommonsChunkPlugin:
// webpack.config.js (webpack 3)
module.exports = {
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: module => module.context && module.context.includes('node_modules')
})
]
};Webpack Runtime Code
The runtime code manages module execution. If you split code into multiple chunks, the runtime is included in the most recent chunk, causing its hash to change whenever any chunk changes.
To avoid this, move the runtime to its own file. In webpack 4 enable it with:
// webpack.config.js (webpack 4)
module.exports = {
optimization: {
runtimeChunk: true
}
};In webpack 3 you can create an empty chunk using CommonsChunkPlugin:
// webpack.config.js (webpack 3)
module.exports = {
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'runtime',
minChunks: Infinity
})
]
};Inline Webpack Runtime to Save an HTTP Request
Inlining the runtime reduces the number of HTTP requests (especially important for HTTP/1). With HtmlWebpackPlugin you can use InlineSourcePlugin:
// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const InlineSourcePlugin = require('html-webpack-inline-source-plugin');
module.exports = {
plugins: [
new HtmlWebpackPlugin({
inlineSource: 'runtime~.+\\.js' // inline all runtime chunks
}),
new InlineSourcePlugin()
]
};If you generate HTML manually, you can read the runtime file on the server and inject its content directly into a <script> tag.
Code Lazy Loading
Prioritize loading critical parts first and lazily load the rest using dynamic import():
// videoPlayer.js
export function renderVideoPlayer() { /* ... */ }
// comments.js
export function renderComments() { /* ... */ }
// index.js
import { renderVideoPlayer } from './videoPlayer';
renderVideoPlayer();
// …custom event listener
onShowCommentsClick(() => {
import('./comments').then(comments => {
comments.renderComments();
});
});⭐️ Note: If you compile with Babel, you need the syntax-dynamic-import plugin to avoid syntax errors.
Split Code by Routes and Pages
For single‑page applications, use import() together with router‑level code splitting (e.g., React Router or Vue Router). For traditional multi‑page apps, define multiple entry points:
// webpack.config.js
module.exports = {
entry: {
home: './src/Home/index.js',
article: './src/Article/index.js',
profile: './src/Profile/index.js'
}
};Webpack will generate a separate bundle for each entry, containing only the modules used by that page.
To avoid duplicate vendor code across bundles, enable optimization.splitChunks.chunks: 'all' in webpack 4 or use CommonsChunkPlugin in webpack 3.
Ensure Stable Module IDs
Webpack assigns numeric IDs to modules, which can shift when new modules are added, causing unnecessary cache invalidation. Use HashedModuleIdsPlugin to base IDs on a hash of the module path:
// webpack.config.js
module.exports = {
plugins: [
new webpack.HashedModuleIdsPlugin()
]
};This keeps IDs stable unless the module’s path changes.
Summary
Cache bundles and use versioned filenames for long‑term caching.
Split bundles into app code, vendor libraries, and runtime.
Inline the runtime to reduce HTTP requests.
Use import() for lazy loading of non‑critical code.
Split code by route or page to avoid loading unnecessary files.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Yuewen Frontend Team
Click follow to learn the latest frontend insights in the cultural content industry. We welcome you to join us.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
