DEV Community

Mohsen Fallahnejad
Mohsen Fallahnejad

Posted on

When Should You Use Dynamic Import in React/Next.js? (With Performance Comparison)

Dynamic import lets you lazy load components so they’re only loaded when needed, instead of bundling everything up front.


1) What is Dynamic Import?

// Static import (always in initial bundle)
import BigChart from "./BigChart"

// Dynamic import (code-split, loaded on demand)
import dynamic from "next/dynamic"
const BigChart = dynamic(() => import("./BigChart"))
Enter fullscreen mode Exit fullscreen mode

2) Why Use It?

Faster initial load (smaller JS)

Conditional loading (only when needed)

Avoid SSR issues for client-only libs ({ ssr: false })


3) Good Use Cases

  • Heavy, optional widgets: charts, maps, rich editors, modals
  • Client-only libs that access window
  • Feature flags / A/B tests
  • Dashboards with many expandable panels

4) Real-World Examples

a) Rich text editor (react-quill)

import dynamic from "next/dynamic"
const RichTextEditor = dynamic(() => import("react-quill"), { ssr: false })

export default function Page() {
  return (
    <div>
      <h1>Create Post</h1>
      <RichTextEditor theme="snow" />
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

b) Charts (react-chartjs-2)

import dynamic from "next/dynamic"
const Chart = dynamic(() => import("react-chartjs-2"), { ssr: false })

export default function Dashboard() {
  return (
    <section>
      <h2>Analytics</h2>
      <Chart type="bar" data={{ labels: ["A","B","C"], datasets: [{ data: [12,19,3] }] }} />
    </section>
  )
}
Enter fullscreen mode Exit fullscreen mode

5) Performance Comparison (Before vs After)

Below is a sample comparison showing how dynamic import can change bundle breakdown.

Use the measurement steps in the next section to produce your actual numbers.

Chunk/Metric Before (static import) After (dynamic import)
Initial JS (first load) 420 KB 240 KB
react-quill in initial bundle 170 KB 0 KB (lazy)
react-chartjs-2 in initial 95 KB 0 KB (lazy)
Route JS (dashboard page) 210 KB 80 KB
Number of JS chunks 9 13
Largest Contentful Paint (LCP)* 2.4s 1.8s

* LCP depends on network/device; treat this as illustrative. Your actual metrics may vary.


6) How to Measure It in Your App

Option A — @next/bundle-analyzer

  1. Install
npm i -D @next/bundle-analyzer
Enter fullscreen mode Exit fullscreen mode
  1. Configure next.config.js
// next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
  enabled: process.env.ANALYZE === 'true',
})

module.exports = withBundleAnalyzer({
  // your Next.js config
})
Enter fullscreen mode Exit fullscreen mode
  1. Build & Analyze
ANALYZE=true next build
# Then open the generated treemaps (usually in .next/analyze) to inspect chunks.
Enter fullscreen mode Exit fullscreen mode

Option B — Inspect build output

next build
# Check "First Load JS shared by all" and per-route sizes in terminal output.
Enter fullscreen mode Exit fullscreen mode

Option C — Web Vitals in production

  • Track LCP/TTFB/CLS before vs after with your analytics (e.g., Vercel Web Analytics, Sentry, or custom).

7) When NOT to Use It

  • Small, shared UI primitives (buttons, inputs)
  • Components present on every route (header, footer)
  • Over-splitting into too many tiny chunks (extra requests/latency)

8) Cheatsheet (TL;DR)

Situation Use Dynamic Import?
Heavy/optional components ✔️ Yes
Client-only libraries (need window) ✔️ Yes
Feature flags / A/B features ✔️ Yes
Always-used layout (navbar/footer) ❌ No
Small common components ❌ No

Pro tip: Pair dynamic imports with skeletons or loading placeholders so the UI feels responsive while chunks load.

Originally published on: Bitlyst

Top comments (1)

Collapse
 
prime_1 profile image
Roshan Sharma

Great Tips!