# How to Implement React Dynamic Import in Next.js to Improve Initial Load Performance

> Learn how to implement React dynamic import in Next.js using next/dynamic. Reduce initial bundle size and improve load performance with server-side rendering and automatic preloading.

- Repository: [Vercel/next.js](https://github.com/vercel/next.js)
- Tags: how-to-guide
- Published: 2026-02-16

---

**Use `next/dynamic` to implement React dynamic import for components, which wraps React.lazy and Suspense with server-side rendering controls and automatic preloading hooks to reduce initial bundle size.**

Next.js provides a robust solution for implementing React dynamic import patterns through its `next/dynamic` API. This utility extends React's native lazy loading capabilities to enable code splitting, server-side rendering awareness, and client-only execution controls. By strategically implementing React dynamic import for heavy components, you can significantly improve initial page load performance while preserving a seamless user experience across server and client environments.

## Architecture of React Dynamic Import in Next.js

### Core Components and Responsibilities

The implementation of React dynamic import in Next.js relies on several interconnected modules within the `packages/next/src/shared/lib/` directory:

- **`dynamic()` function** (in [`dynamic.tsx`](https://github.com/vercel/next.js/blob/main/dynamic.tsx)): Parses the loader function or options object, creates a Loadable component, and wires SSR flags. It handles the `convertModule()` logic (lines 34-36) to normalize exports and the `noSSR()` helper (lines 53-71) for client-only components.

- **`Loadable` factory** (in [`loadable.shared-runtime.tsx`](https://github.com/vercel/next.js/blob/main/loadable.shared-runtime.tsx)): Implements the runtime loading logic using the react-loadable pattern. It manages promise handling, delay/timeout configurations, and error boundaries. This is where the `preload()` method is defined (line 56).

- **`LoadableContext`** (in [`loadable-context.shared-runtime.ts`](https://github.com/vercel/next.js/blob/main/loadable-context.shared-runtime.ts)): Propagates module IDs during client-side hydration, enabling the server to execute `preloadReady` for modules that were already rendered.

- **[`dynamic-bailout-to-csr.tsx`](https://github.com/vercel/next.js/blob/main/dynamic-bailout-to-csr.tsx)**: Provides the fallback component used when SSR is disabled, ensuring the component only renders on the client.

### Execution Flow

When you implement React dynamic import using `next/dynamic`, the following sequence occurs:

1. **User invocation**: You call `dynamic(() => import('./Heavy'))` or pass an options object.
2. **Option parsing**: The `dynamic()` function detects whether the first argument is a Promise, function, or options object, then builds a Loadable configuration.
3. **Module normalization**: The loader wraps the resolved value using `convertModule()` to ensure it always returns an object with a `default` export.
4. **Subscription creation**: `Loadable` creates a `LoadableSubscription` that tracks loading state (`loading`, `loaded`, `error`) and notifies React via `useSyncExternalStore`.
5. **Client rendering**: While the promise resolves, the component renders a loading UI (if supplied), then renders the imported component via `React.createElement`.
6. **SSR handling**: If `ssr: false` is passed, the server returns only the loading placeholder, preventing the component from being rendered during SSR and avoiding unnecessary data transfer.

## Implementing React Dynamic Import: Practical Examples

### Basic Lazy Component Loading

The simplest way to implement React dynamic import is to pass an import function to `next/dynamic`:

```tsx
import dynamic from 'next/dynamic'

const Heavy = dynamic(() => import('../components/Heavy'))

export default function Page() {
  return (
    <>
      <h1>My Page</h1>
      {/* Heavy is rendered only when this part of the UI is reached */}
      <Heavy />
    </>
  )
}

```

The loader function returns a promise; `next/dynamic` wraps it with the normalization and subscription logic described in the architecture section.

### Custom Loading Indicators

You can provide a custom loading component using the `loading` option. According to the type definitions in [`dynamic.tsx`](https://github.com/vercel/next.js/blob/main/dynamic.tsx) (lines 23-29), the loading component receives `DynamicOptionsLoadingProps`:

```tsx
import dynamic from 'next/dynamic'

const Chart = dynamic(() => import('../components/Chart'), {
  loading: ({ isLoading }) => (isLoading ? <p>Loading chart…</p> : null),
})

```

This renders your custom UI while the module is being fetched, improving perceived performance.

### Disabling Server-Side Rendering

For components that depend on browser-only APIs (like `window` or `document`), implement React dynamic import with `ssr: false`. The `noSSR()` helper in [`dynamic.tsx`](https://github.com/vercel/next.js/blob/main/dynamic.tsx) (lines 53-71) disables webpack module hints and ensures the component only renders on the client:

```tsx
import dynamic from 'next/dynamic'

const Map = dynamic(() => import('../components/Map'), {
  ssr: false,
})

```

Because `ssr` is false, the server sends only the loading placeholder, and the map bundle is fetched and executed after hydration.

### Programmatic Preloading

To optimize user interactions, preload components before they are needed. The `LoadableComponent` exposes a `preload()` method defined in [`loadable.shared-runtime.tsx`](https://github.com/vercel/next.js/blob/main/loadable.shared-runtime.tsx) (line 56):

```tsx
import dynamic from 'next/dynamic'

const Modal = dynamic(() => import('../components/Modal'))

// Somewhere in a click handler
function openModal() {
  Modal.preload() // Triggers the import early
  // later …
}

```

This triggers the dynamic import early, reducing latency when the component actually renders.

## Key Source Files in next/dynamic

The React dynamic import implementation in Next.js spans several critical files:

| File | Role | Link |
|------|------|------|
| [`packages/next/src/shared/lib/dynamic.tsx`](https://github.com/vercel/next.js/blob/main/packages/next/src/shared/lib/dynamic.tsx) | Public `dynamic` API, option parsing, SSR handling | [dynamic.tsx](https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/dynamic.tsx) |
| [`packages/next/src/shared/lib/loadable.shared-runtime.tsx`](https://github.com/vercel/next.js/blob/main/packages/next/src/shared/lib/loadable.shared-runtime.tsx) | Runtime implementation of Loadable (subscription, preloading) | [loadable.shared-runtime.tsx](https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/loadable.shared-runtime.tsx) |
| [`packages/next/src/shared/lib/loadable-context.shared-runtime.ts`](https://github.com/vercel/next.js/blob/main/packages/next/src/shared/lib/loadable-context.shared-runtime.ts) | Context for capturing module IDs during hydration | [loadable-context.shared-runtime.ts](https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/loadable-context.shared-runtime.ts) |
| [`packages/next/src/shared/lib/lazy-dynamic/dynamic-bailout-to-csr.tsx`](https://github.com/vercel/next.js/blob/main/packages/next/src/shared/lib/lazy-dynamic/dynamic-bailout-to-csr.tsx) | Fallback component used when SSR is disabled | [dynamic-bailout-to-csr.tsx](https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/lazy-dynamic/dynamic-bailout-to-csr.tsx) |
| [`packages/next/src/api/dynamic.ts`](https://github.com/vercel/next.js/blob/main/packages/next/src/api/dynamic.ts) | Exported entry point for the `next/dynamic` package | [dynamic.ts](https://github.com/vercel/next.js/blob/canary/packages/next/src/api/dynamic.ts) |

These files together constitute the full stack that enables React dynamic import in a Next.js application, allowing developers to improve initial load performance while preserving server-side rendering capabilities.

## Summary

Implementing React dynamic import in Next.js through the `next/dynamic` API provides a robust mechanism for optimizing initial load performance:

- **Code-splitting by default**: Components loaded via `dynamic()` are automatically split into separate chunks, reducing the initial JavaScript bundle size.
- **SSR-aware loading**: The architecture supports full server-side rendering while allowing client-only execution through the `ssr: false` option, implemented via the `noSSR()` helper in [`dynamic.tsx`](https://github.com/vercel/next.js/blob/main/dynamic.tsx).
- **Loading state management**: Custom loading components receive `DynamicOptionsLoadingProps` (defined in [`dynamic.tsx`](https://github.com/vercel/next.js/blob/main/dynamic.tsx) lines 23-29) to render appropriate UI during module fetching.
- **Programmatic preloading**: The `preload()` method (available on Loadable components via [`loadable.shared-runtime.tsx`](https://github.com/vercel/next.js/blob/main/loadable.shared-runtime.tsx) line 56) enables eager fetching of modules before they are rendered.
- **Runtime architecture**: The system relies on `LoadableSubscription` (in [`loadable.shared-runtime.tsx`](https://github.com/vercel/next.js/blob/main/loadable.shared-runtime.tsx)) and `LoadableContext` (in [`loadable-context.shared-runtime.ts`](https://github.com/vercel/next.js/blob/main/loadable-context.shared-runtime.ts)) to manage loading states and hydration across server and client.

## Frequently Asked Questions

### How does React dynamic import differ between standard React and Next.js?

Standard React uses `React.lazy()` combined with `Suspense` to implement dynamic imports, but this approach lacks server-side rendering support and requires manual handling of loading states. Next.js extends this pattern through `next/dynamic`, which adds SSR awareness via the `ssr` option, automatic loading component support through `DynamicOptionsLoadingProps`, and preloading capabilities via the `preload()` method. The implementation spans files like [`dynamic.tsx`](https://github.com/vercel/next.js/blob/main/dynamic.tsx) and [`loadable.shared-runtime.tsx`](https://github.com/vercel/next.js/blob/main/loadable.shared-runtime.tsx) to provide a unified API that works across server and client environments.

### When should I use the `ssr: false` option in Next.js dynamic imports?

Use `ssr: false` when your component depends on browser-only APIs such as `window`, `document`, or navigator properties that are undefined during server-side rendering. According to the source code in [`dynamic.tsx`](https://github.com/vercel/next.js/blob/main/dynamic.tsx) (lines 53-71), the `noSSR()` helper disables webpack module hints and returns a placeholder that only renders on the client after hydration. This prevents hydration mismatches and reduces server payload size by excluding the component from the initial HTML.

### How can I preload a dynamically imported component before the user needs it?

Next.js exposes a `preload()` method on components created with `next/dynamic`, allowing you to trigger the import promise before the component renders. As implemented in [`loadable.shared-runtime.tsx`](https://github.com/vercel/next.js/blob/main/loadable.shared-runtime.tsx) (line 56), calling `Component.preload()` executes the loader function immediately, caching the result. This is useful for prefetching heavy components on user interactions like hover or click before the actual navigation or display occurs.

### What loading options are available when implementing React dynamic import in Next.js?

The `next/dynamic` API accepts a `loading` option that receives `DynamicOptionsLoadingProps`, defined in [`dynamic.tsx`](https://github.com/vercel/next.js/blob/main/dynamic.tsx) (lines 23-29). This allows you to render custom UI based on the `isLoading` state, handle loading errors, or delay the loading indicator to prevent flickering on fast networks. You can also omit the loading option to render nothing while the module fetches, or provide a static component that displays immediately during the import process.