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.
183 lines
4.7 KiB
Markdown
183 lines
4.7 KiB
Markdown
# 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:
|
|
|
|
```tsx
|
|
// 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:
|
|
|
|
```js
|
|
// 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:
|
|
|
|
```tsx
|
|
// 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.
|
|
|
|
```tsx
|
|
// 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.
|
|
|
|
```tsx
|
|
// 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
|
|
|
|
```js
|
|
// 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+):
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```js
|
|
// 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
|