BitlystBitlyst
PostsSeriesTopicsAbout

Bitlyst

Short, diagram-first posts on JavaScript, React & Next.js. Follow via RSS or the newsletter.

© 2026 Mohsen Fallahnejad

PostsSeriesTopicsLinksAboutRSSGitHubX
Posts/react
Intermediate3 min read

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

A practical guide to dynamic imports with real-world examples and a before/after bundle-size comparison using next-bundle-analyzer.

Mohsen Fallahnejad

Mohsen Fallahnejad

Sep 17, 20251 readsDiscuss

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?#

tsx
// 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"))

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)#

tsx
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>
  )
}

b) Charts (react-chartjs-2)#

tsx
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>
  )
}

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/MetricBefore (static import)After (dynamic import)
Initial JS (first load)420 KB240 KB
react-quill in initial bundle170 KB0 KB (lazy)
react-chartjs-2 in initial95 KB0 KB (lazy)
Route JS (dashboard page)210 KB80 KB
Number of JS chunks913
Largest Contentful Paint (LCP)*2.4s1.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
bash
npm i -D @next/bundle-analyzer
  1. Configure next.config.js
js
// next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
  enabled: process.env.ANALYZE === 'true',
})

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

Option B — Inspect build output#

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

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)#

SituationUse 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.

Filed under#react#nextjs#dynamic-import#performance#bundle-size

Was this worth your time?

One tap tells me what to write more of.

Mohsen Fallahnejad

Written by

Mohsen Fallahnejad

Frontend engineer explaining JavaScript, React & Next.js internals in posts you can finish over coffee. More about me

X ↗GitHub ↗LinkedIn ↗
← OlderGit Rebase vs Git Merge — Simple GuideNewer →React useRef vs useState — When to Use Which

Start the discussion

Questions, corrections, a better approach? I read and reply to every comment.

Read next

I Optimized a Button From O(n) to O(1). The Speed Didn't Matter.

A bug report, a route map, and three rounds of making it faster — a story about what Big O really measures, why constants win at small n, and why the best algorithm is sometimes deleting the question.

Oct 31, 202610 min read6

Scrolling My Blog Index Prefetched All 51 Posts. One Config Line Cut It to 90 kB.

What a long list of Next.js <Link>s really costs in prefetch requests, measured on this blog, and how partialPrefetching shares one App Shell per route instead.

Nov 3, 20264 min read

The Server Action That Ran Twice, but Only on localhost

Why a Next.js Server Action called from useEffect runs twice in development: React Strict Mode mounts every component twice. What that reveals, and where the call belongs.

Nov 3, 20264 min read

Get new posts in your inbox

No spam. Unsubscribe any time.

3 min left
Discuss👍 React
👍