Next.js applications can suffer performance hits when they ship too much JavaScript. Large bundles increase download, parse and execution time, delaying First Contentful Paint (FCP) and Time to Interactive (TTI). That also pushes back ad slots and other third-party scripts.
Why we quote minified size only
All size figures in this article refer to the minified but uncompressed footprint of a package as reported by Bundlephobia (sizes verified in July 2025). While on‑the‑wire gzip/brotli can shrink bytes transferred, the browser still has to parse every minified byte, so parse & execution cost correlate with the minified size. Using a single metric keeps comparisons clear and avoids mixing incomparable numbers.
Replacing Large Libraries with Smaller Alternatives
Third‑party libraries often dominate a bundle. Replacing a heavyweight dependency with a lighter API‑compatible one can dramatically cut size.
Library | Size | Reduction |
---|---|---|
moment | 295 kB | baseline |
date‑fns (v4) | 77 kB | - 76 % |
dayjs | 7 kB | - 96 % |
Beyond its large size and mutable API, moment is now considered a legacy project by its creators, who recommend against its use in new development. A modern alternative like dayjs is not only dramatically smaller but also maintains a very similar API, making migration straightforward and highly recommended.
Use Bundlephobia before npm install
Bundlephobia shows install size, minified size, dependency tree, and export surface. Paste a prospective package into the search to see its raw cost and compare alternatives.
Ask LLMs for Alternatives
Prompt your favourite LLM with a list of current client‑side dependencies and ask for smaller replacements, opportunities to move the work server‑side, and migration effort estimates.
I have the following client-side libraries in a next.js app:
{copy-paste your libraries here}
examine each library and let me know if:
* we can get rid of any of them to save up on bundle size
* suggest alternative libraries to move some things to the server side
* suggest alternative libraries that has same functionality but has smaller bundle size
* if an alternative library is provided consider how hard it is to migrate to that library and keep in mind how much we can reduce the minified bundle size by this
Such queries can yield suggestions like Day.js, date-fns, or Luxon as alternatives to Moment.js Using AI in this way can jump-start your search for performance-friendly libraries.
Avoiding Wildcard (import *) Imports
Wildcard (“star”) imports sabotage tree‑shaking for CommonJS packages because the bundler has to keep the entire export object alive at runtime. The hit is less obvious with pure‑ESM libraries, but any namespace import still forces extra property enumeration overhead. Although pure-ESM libraries are still not that common.
Named imports tree‑shake best - they give the bundler static knowledge of what is used.
// Good - pulls in just debounce from lodash/debounce (~1.8 kB)
import debounce from 'lodash/debounce';
// Good with ESM libs - only the function is kept
import { format } from 'date-fns';
Star imports block dead‑code elimination - every export is considered a live property.
// Bad - includes the whole library (~70 kB)
import * as _ from 'lodash';
Tooling fixes
- ESLint - add import/no-namespace to fail on star imports.
- Side‑effects declarations - ensure internal libraries list "sideEffects": false in package.json so bundlers can drop unused files.
Lodash Optimisation and Tree‑Shaking
Lodash remains popular for its stable API and rich utility set, but the monolithic lodash package (~70 KB minified) is rarely justified in 2025 apps. Choose one optimisation strategy and apply it consistently across the codebase:
Strategy | How | Pros | Cons |
---|---|---|---|
Per‑method import | import debounce from 'lodash/debounce' | Zero config, explicit | Verbose paths, must know file locations |
lodash-es + tree‑shaking | import from 'lodash-es’ | Clean syntax, auto‑drops unused helpers | Requires ESM‑aware bundler; potential CJS duplicates - see the Watch out for duplications for more details |
Alias lodash => lodash-es | webpack/Turbopack alias: "lodash": "lodash-es" | One‑line migration | Node (CJS) code may break (e.g. Server-side or tooling scripts that still use require('lodash')) |
Babel/SWC transform | babel-plugin-lodash / @swc/plugin-transform-imports | Automated, works with existing CJS imports | Build‑time dependency, plugins are not maintained anymore |
Sample alias in Next.js
Next.js >= 14 uses Turbopack in dev and webpack 5 in production, so wire the alias into both toolchains:
// next.config.js (CommonJS)
/** @type {import('next').NextConfig} */
const nextConfig = {
// Production (webpack)
webpack(config) {
config.resolve.alias = {
...(config.resolve.alias || {}),
lodash: 'lodash-es',
};
return config;
},
// Development (Turbopack dev)
experimental: {
// Deprecated in Next 15 – switch to top‑level `turbopack` once you upgrade
turbo: {
resolveAlias: {
lodash: 'lodash-es',
},
},
},
// Forward‑looking config (Next 15 full Turbopack build)
turbopack: {
resolveAlias: {
lodash: 'lodash-es',
},
},
};
module.exports = nextConfig;
If you’re on the Next 15 canary (full Turbopack build), only the experimental.turbo.resolveAlias block is needed.
Watch out for duplication
lodash-es saves on bundle size only when no other code path pulls in CommonJS Lodash. Duplicates creep in when:
- Legacy imports in your codebase still do require('lodash') or import _ from 'lodash'.
- Third‑party dependencies somewhere in node_modules require CJS Lodash.
- Mixed strategies - teammates import lodash/debounce while others use lodash-es.
- Aliasing gaps - you alias in Turbopack/webpack but forget Jest/Vite, so test bundles pull CJS Lodash.
How to detect & fix
- Analyse the bundle: the best approach is to use the official, integrated solution: @next/bundle-analyzer. After installing it, you can enable it in your next.config.js file. This will automatically generate a visual map of your client and server bundles during the build process, helping you pinpoint exactly where the bloat is coming from.
- List dependents: npm ls lodash shows which packages pull CJS Lodash.
- Patch stubborn deps with patch-package or submit a PR upstream.
Even one stray require('lodash') can add ~70 KB minified, so treat duplicates as a CI‑failing lint error.
By thoughtfully applying these techniques - from picking lighter libraries to fine-tuning imports - you can achieve substantial performance gains. Modern web apps demand speed, especially on mobile devices and you have many tools at your disposal to trim the fat from your Next.js bundles. Measure your improvements (use Lighthouse, Web Vitals, or Next.js’s build size analysis) and iterate. The end result will be a faster, snappier application that delights users and makes the most of what Next.js has to offer.
If you're concerned about your site's bundle sizes and want to ensure the best possible performance, reach out to us at Catch Metrics! We can help.