JS Code Splitting Advanced Performance Guide

Go beyond basic route splitting with shared chunks, vendor extraction, magic comments, and bundle analysis. Learn advanced code splitting patterns that measurably improve load performance.

7 min read

Advanced JavaScript code splitting builds on the basics to squeeze every millisecond out of page loads. While route-level splitting separates code by page, advanced patterns tackle the shared dependencies between routes, the third-party libraries that bloat every bundle, and the fine-tuning of how and when chunks load.

The payoff is real: well-split applications can cut first-load JavaScript by another thirty to fifty percent beyond basic route splitting.

Advanced code splitting decision flow

Each stage removes more unnecessary bytes from the initial download. The diagram shows the natural progression from basic to advanced splitting.

Extracting Shared Chunks

When multiple routes import the same module, bundlers can extract that module into a shared chunk that loads once and caches across pages. Without this, every route bundle includes its own copy of the dependency.

Consider an app where both the dashboard and settings pages use a date formatting library:

javascriptjavascript
// dashboard.js
import { format } from "date-fns";
 
// settings.js
import { format } from "date-fns";

Without shared chunk extraction, date-fns ends up duplicated in both route bundles. With proper configuration, the bundler pulls it into a single shared file that both routes reference.

In Vite, shared chunk extraction is automatic for dependencies in node_modules. In Webpack, use the SplitChunksPlugin configuration. Start with the basic setup that enables splitting for all chunks:

javascriptjavascript
// webpack.config.js
module.exports = {
  optimization: {
    splitChunks: {
      chunks: "all",
      minSize: 20000,
    },
  },
};

For vendor-specific extraction, add a cacheGroup that targets the node_modules directory. This pulls all third-party code into a dedicated chunk that caches independently of your application code:

javascriptjavascript
// Continued webpack config
cacheGroups: {
  vendors: {
    test: /node_modules/,
    name: "vendors",
    chunks: "all",
  },
},

The configuration tells Webpack to extract every dependency from node_modules into a shared vendors chunk. Any route that needs lodash, React, or any other third-party library loads the same cached file.

Separating Vendor Code

Vendor splitting takes shared chunk extraction further by specifically targeting third-party libraries. The key insight: your app code changes frequently, but React, date-fns, and other dependencies change rarely. Separating them means users download vendor code once and keep it cached across deployments.

A good vendor split creates three layers of chunks:

LayerContentsCache duration
VendorReact, lodash, and other npm dependenciesMonths (changes rarely)
CommonShared app utilities used across routesDays to weeks
RoutePage-specific code loaded on demandChanges per deployment

In Vite, vendor splitting is automatic. The bundler groups dependencies by their origin and creates stable chunk names based on the package name.

For Webpack, the configuration above handles it. For Rollup, use the manualChunks option to define groups explicitly.

Magic Comments for Fine Control

Webpack supports magic comments inside dynamic import calls. These inline directives control chunk naming, prefetch behavior, and loading priority without touching the webpack config file. Vite and Rollup do not support these comments natively, so if you use Vite, skip ahead to the manualChunks approach in the previous section.

The most useful Webpack magic comment is webpackChunkName, which gives a chunk a predictable name instead of a random hash:

javascriptjavascript
// Without magic comment → chunk filename is a hash
import("./dashboard.js");
 
// With magic comment → chunk filename includes the name
import(/* webpackChunkName: "dashboard" */ "./dashboard.js");

Named chunks make bundle analysis and debugging much easier. Webpack can also prefetch chunks the user is likely to need next:

javascriptjavascript
// Prefetch: loads after the main page finishes, low priority
import(/* webpackPrefetch: true */ "./likely-next-page.js");
 
// Preload: loads alongside the main page, high priority
import(/* webpackPreload: true */ "./critical-next-page.js");

Prefetch downloads the chunk when the browser is idle. Preload downloads it immediately. Use prefetch for pages the user might visit, and preload for resources needed within seconds.

Bundle Analysis Tools

You cannot improve what you do not measure. Bundle analysis tools visualize your output files, showing exactly which modules contribute to each chunk and highlighting duplication opportunities.

For Webpack, use webpack-bundle-analyzer. It generates an interactive treemap showing every module's size relative to the total bundle:

texttext
npx webpack --profile --json > stats.json
npx webpack-bundle-analyzer stats.json

For Vite and Rollup, use rollup-plugin-visualizer. It produces a similar treemap that reveals bloated dependencies and missing splits.

When analyzing, look for three red flags:

  • A single chunk larger than 200 KB gzipped
  • The same library appearing in multiple route chunks
  • A vendor chunk that contains mostly app code (meaning the split is ineffective)

Address each finding by adjusting your splitChunks configuration or adding magic comments. Then re-analyze to confirm the improvement.

Measuring the Real-World Impact

Configuration changes only matter if they improve actual load times. Use these metrics to validate your splits:

  • First Contentful Paint: The time until the first content appears. Smaller initial bundles improve this directly.
  • Largest Contentful Paint: The time until the main content loads. Affected by critical-path JavaScript size.
  • JavaScript execution time: Smaller chunks parse and execute faster, especially on mobile devices.

Run Lighthouse or PageSpeed Insights before and after changing your splitting strategy. Document the baseline and the improvement for each change you make.

For more on the fundamentals, see dynamic imports. For library-specific configuration, the tree shaking guide explains how to eliminate unused exports from your bundles.

Rune AI

Rune AI

Key Insights

  • Shared chunks extract common dependencies used by multiple routes into cacheable bundles.
  • Vendor splitting separates third-party libraries from your app code for better caching.
  • Webpack magic comments like webpackChunkName and webpackPrefetch give you fine control over chunk behavior.
  • Bundle analysis tools like webpack-bundle-analyzer and rollup-plugin-visualizer reveal optimization opportunities.
  • Measure performance before and after splitting to confirm real-world improvement.
RunePowered by Rune AI

Frequently Asked Questions

What is the difference between basic and advanced code splitting?

Basic code splitting splits code by route using dynamic imports. Advanced code splitting goes further: extracting shared dependencies into common chunks, separating vendor libraries from app code, using magic comments for naming and prefetching, and measuring the impact with bundle analysis tools.

Does code splitting help if my app only has one page?

Yes. Even single-page apps benefit from splitting vendor libraries (React, lodash, etc.) into separate chunks. This enables better caching: users download vendor code once, and only download updated app code on subsequent visits.

What is a good bundle size target?

Aim for individual chunks under 100 KB (gzipped). If a route chunk exceeds that, consider further splitting. The initial page load should ideally stay under 200 KB of JavaScript total.

Conclusion

Advanced code splitting turns a good loading experience into a great one. By extracting shared chunks, separating vendors, and using magic comments for fine-grained control, you push JavaScript performance beyond the basics. Combine these techniques with bundle analysis to measure the real impact and catch regressions before they ship.