Files
agent-skills/skills/next-best-practices/bundling.md
Jason Woltje f6bcc86881 feat: Add 5 curated skills for Mosaic Stack
New skills:
- next-best-practices: Next.js 15+ RSC, async patterns, self-hosting (vercel-labs)
- better-auth-best-practices: Official Better-Auth with Drizzle adapter (better-auth)
- verification-before-completion: Evidence-based completion claims (obra/superpowers)
- shadcn-ui: Component patterns with Tailwind v4 adaptation note (developer-kit)
- writing-skills: TDD methodology for skill authoring (obra/superpowers)

README reorganized by category with Mosaic Stack alignment section.
Total: 9 skills (4 existing + 5 new).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:17:40 -06:00

4.4 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