A charting library like Recharts (180KB gzipped) or a rich text editor like Monaco (2MB) statically imported in a shared layout loads on every page — even the marketing homepage, authentication screens, and error pages that never render a chart or editor. This adds hundreds of milliseconds of unnecessary parse time to routes where the library is invisible. ISO 25010 performance-efficiency.time-behaviour requires time-behaviour to be proportionate to the task being performed; serving 2MB of editor JavaScript to a user reading a blog post is not proportionate.
Low because heavy static imports increase parse time on unrelated pages, but they do not prevent functionality and often go unnoticed until measured.
Move heavy library imports from static top-level imports to dynamic imports scoped to the component or route that actually uses them. Use next/dynamic in Next.js or React.lazy elsewhere, combined with a skeleton placeholder.
// Before — loads Monaco on every page
import MonacoEditor from '@monaco-editor/react'
// After — loads only on /editor route
import dynamic from 'next/dynamic'
const MonacoEditor = dynamic(() => import('@monaco-editor/react'), {
loading: () => <Skeleton className="h-64 w-full" />,
ssr: false // Monaco doesn't support SSR
})
// Before — Recharts in layout.tsx
import { BarChart, BarChartData } from 'recharts'
// After — in the specific dashboard component
const AnalyticsChart = dynamic(
() => import('./AnalyticsChart'), // AnalyticsChart wraps recharts
{ loading: () => <Skeleton />, ssr: false }
)
Verify the split worked by confirming recharts/monaco no longer appears in the main chunk in your bundle analyzer output.
ID: performance-load.bundle.dynamic-imports
Severity: low
What to look for: List all dependencies over 100KB bundled size (common examples: recharts, chart.js, pdf-lib, monaco-editor, draft-js, quill, three.js, mapbox-gl). For each, count the number of pages/routes that import it. If a library is imported in a shared layout or top-level file but used on fewer than 50% of routes, it should use a dynamic import. Enumerate: "X heavy libraries detected, Y are dynamically imported."
Pass criteria: At least 80% of heavy libraries (over 100KB bundled) that are used on fewer than 50% of routes are loaded via dynamic imports (next/dynamic, React.lazy, or equivalent). No more than 1 heavy library is statically imported in a shared layout while used on only 1-2 pages. Report: "X of Y heavy libraries use dynamic imports."
Fail criteria: 2 or more large libraries over 100KB are imported at the top level of every page (via shared layout or global import), even though fewer than 50% of pages use them.
Skip (N/A) when: Project has no dependencies over 100KB bundled, or total bundle is under 100KB gzipped.
Detail on fail: "0 of 3 heavy libraries use dynamic imports — recharts (180KB) imported in layout.tsx but used only on /dashboard, pdf-lib (200KB) used only on /export" or "monaco-editor (2MB) statically imported in app layout but only used in /editor route"
Remediation: Use dynamic imports for heavy libraries:
// Before — loaded everywhere
import { BarChart } from 'recharts'
export default function App() { ... }
// After — loaded only when needed
const AnalyticsChart = dynamic(() => import('./AnalyticsChart'), {
loading: () => <Skeleton />,
ssr: false // disable SSR if not needed
})
export default function App() {
return <AnalyticsChart />
}