optimizePackageImports

Load only the modules you actually import from barrel-file packages.

Signature
experimental?: { optimizePackageImports?: string[] }

Parameters

ParameterTypeDescription
$optimizePackageImportsoptionalstring[]= []Package names whose barrel-file imports should be rewritten so only the modules actually used are loaded. Wildcard entries such as 'react-icons/*' are used in the built-in default list.

Many libraries expose one entry point that re-exports every module in the package. Importing two icons from such a barrel pulls the whole index into the module graph: thousands of modules to resolve and compile on every dev refresh, and dead weight the production bundler has to prove is unused. optimizePackageImports rewrites those imports to point straight at the individual modules.

When to reach for it

Reach for it when a dev rebuild is slow and the module count is dominated by one dependency — an icon set, a component kit, a utility library — and when you would otherwise be tempted to write deep imports like import Icon from 'pkg/dist/icons/Icon' by hand. This option gives you the same result while keeping readable named imports.

What trips people up

  • The popular libraries are already enabled by default: lucide-react, date-fns, lodash-es, @mui/material, @mui/icons-material, @headlessui/react, @heroicons/react/*, recharts, ramda, antd, react-icons/*, rxjs and more. Adding them again changes nothing; measure before assuming this option is your missing win.
  • It only helps imports from the package root. Deep imports are already direct.
  • The feature is experimental. A barrel with side effects — a global stylesheet, a registration call — can behave differently once it is bypassed.

Example

Example · typescript
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  experimental: {
    optimizePackageImports: ['@acme/icons'],
  },
}

export default nextConfig

When to use it

  • Speed up dev rebuilds in an app that imports a handful of icons from a very large icon package.
  • Trim an in-house design system's barrel file out of the production bundle.
  • Replace hand-written deep imports that break whenever the dependency reorganizes its files.

Frequently asked questions

Which packages does optimizePackageImports already handle?

About thirty are enabled by default, including lucide-react, date-fns, lodash-es, ramda, antd, @mui/material, @mui/icons-material, @headlessui/react, @heroicons/react/20/solid, @heroicons/react/24/solid, @heroicons/react/24/outline, @tabler/icons-react, recharts, rxjs, react-use and react-icons/*. Adding one of those to the array is a no-op.

Does optimizePackageImports reduce the production bundle or just dev speed?

Both, but the effect is usually most visible in development, where the barrel file forces the dev server to compile thousands of modules on each refresh. In production it removes modules the bundler would otherwise have to analyze before discarding.

Is optimizePackageImports safe to use?

It is still marked experimental, so treat it as such and verify the affected imports after enabling it. The risk case is a barrel file with side effects — importing a global stylesheet or registering something — which is skipped once imports are rewritten.

More examples

The import shape this affects

Without the option, @acme/icons/index re-exports every icon in the package, so the dev server compiles all of them to render two. With the package listed, the import resolves to the two icon modules and the rest never enter the graph — the source code does not change.

Example · tsx
// Stays readable, but only these two modules are loaded
import { ArrowRight, Check } from '@acme/icons'

export function Row({ label }: { label: string }) {
  return (
    <li>
      <Check />
      {label}
      <ArrowRight />
    </li>
  )
}

Adding a package the default list does not cover

Only in-house or less common libraries need listing. A config full of already-defaulted names suggests the slow dependency was never identified, and hides the one entry that is doing real work.

Example · typescript
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  experimental: {
    // lucide-react, @mui/material, date-fns, react-icons/* and friends
    // are optimized by default — no entry needed for them
    optimizePackageImports: ['@acme/icons', '@acme/design-system'],
  },
}

export default nextConfig

The manual alternative you no longer need

Deep imports work but hard-code the package's internal paths, so a minor version that moves dist/ breaks every call site. Listing the package keeps the public named import and lets Next.js do the rewrite.

Example · tsx
// Deep imports achieve the same thing by hand
import ArrowRight from '@acme/icons/dist/arrow-right'
import Check from '@acme/icons/dist/check'

// With optimizePackageImports, this is equivalent and survives
// a change in the package's internal file layout
import { ArrowRight as A, Check as C } from '@acme/icons'

Discussion

  • Be the first to comment on this lesson.