Files
stack/packages/mosaic/framework/skills/next-best-practices/bundling.md
T
fargo 1a822493ba format: apply repo prettier (3.8.1) to the folded skills tree
963 markdown files reformatted with the repository's pinned prettier so
pnpm format:check covers the folded tree like every other repo file.

The formatter's embedded-language pass also normalized code fences
(TS semicolons, closed HTML tags in examples, lowercased CSS hex colors,
one renumbered list that skipped an index). Alphanumeric token deltas vs
the fold commit were audited file-by-file; all are formatter-equivalent
markup normalizations plus the four sanitized skills.
2026-08-19 14:37:17 -05:00

4.7 KiB

Bundling

Fix common bundling issues with third-party packages.

Server-Incompatible Packages

Some packages use browser APIs (window, document, localStorage) and fail in Server Components.

Error Signs

ReferenceError: window is not defined
ReferenceError: document is not defined
ReferenceError: localStorage is not defined
Module not found: Can't resolve 'fs'

Solution 1: Mark as Client-Only

If the package is only needed on client:

// Bad: Fails - package uses window
import SomeChart from 'some-chart-library';

export default function Page() {
  return <SomeChart />;
}

// Good: Use dynamic import with ssr: false
import dynamic from 'next/dynamic';

const SomeChart = dynamic(() => import('some-chart-library'), {
  ssr: false,
});

export default function Page() {
  return <SomeChart />;
}

Solution 2: Externalize from Server Bundle

For packages that should run on server but have bundling issues:

// next.config.js
module.exports = {
  serverExternalPackages: ['problematic-package'],
};

Use this for:

  • Packages with native bindings (sharp, bcrypt)
  • Packages that don't bundle well (some ORMs)
  • Packages with circular dependencies

Solution 3: Client Component Wrapper

Wrap the entire usage in a client component:

// components/ChartWrapper.tsx
'use client';

import { Chart } from 'chart-library';

export function ChartWrapper(props) {
  return <Chart {...props} />;
}

// app/page.tsx (server component)
import { ChartWrapper } from '@/components/ChartWrapper';

export default function Page() {
  return <ChartWrapper data={data} />;
}

CSS Imports

Import CSS files instead of using <link> tags. Next.js handles bundling and optimization.

// Bad: Manual link tag
<link rel="stylesheet" href="/styles.css" />;

// Good: Import CSS
import './styles.css';

// Good: CSS Modules
import styles from './Button.module.css';

Polyfills

Next.js includes common polyfills automatically. Don't load redundant ones from polyfill.io or similar CDNs.

Already included: Array.from, Object.assign, Promise, fetch, Map, Set, Symbol, URLSearchParams, and 50+ others.

// Bad: Redundant polyfills
<script src="https://polyfill.io/v3/polyfill.min.js?features=fetch,Promise,Array.from" />

// Good: Next.js includes these automatically

ESM/CommonJS Issues

Error Signs

SyntaxError: Cannot use import statement outside a module
Error: require() of ES Module
Module not found: ESM packages need to be imported

Solution: Transpile Package

// next.config.js
module.exports = {
  transpilePackages: ['some-esm-package', 'another-package'],
};

Common Problematic Packages

Package Issue Solution
sharp Native bindings serverExternalPackages: ['sharp']
bcrypt Native bindings serverExternalPackages: ['bcrypt'] or use bcryptjs
canvas Native bindings serverExternalPackages: ['canvas']
recharts Uses window dynamic(() => import('recharts'), { ssr: false })
react-quill Uses document dynamic(() => import('react-quill'), { ssr: false })
mapbox-gl Uses window dynamic(() => import('mapbox-gl'), { ssr: false })
monaco-editor Uses window dynamic(() => import('@monaco-editor/react'), { ssr: false })
lottie-web Uses document dynamic(() => import('lottie-react'), { ssr: false })

Bundle Analysis

Analyze bundle size with the built-in analyzer (Next.js 16.1+):

next experimental-analyze

This opens an interactive UI to:

  • Filter by route, environment (client/server), and type
  • Inspect module sizes and import chains
  • View treemap visualization

Save output for comparison:

next experimental-analyze --output
# Output saved to .next/diagnostics/analyze

Reference: https://nextjs.org/docs/app/guides/package-bundling

Migrating from Webpack to Turbopack

Turbopack is the default bundler in Next.js 15+. If you have custom webpack config, migrate to Turbopack-compatible alternatives:

// next.config.js
module.exports = {
  // Good: Works with Turbopack
  serverExternalPackages: ['package'],
  transpilePackages: ['package'],

  // Bad: Webpack-only - migrate away from this
  webpack: (config) => {
    // custom webpack config
  },
};

Reference: https://nextjs.org/docs/app/building-your-application/upgrading/from-webpack-to-turbopack