Table of Contents

In standard Next.js compilation, the framework evaluates whether a route is static (pre-rendered at build time) or dynamic (rendered on demand per request) based on heuristics. If you call functions like cookies(), headers(), or read searchParams directly inside a component, the compiler flags the entire page as dynamic.

In Next.js 15/16, Vercel introduces the experimental dynamicIO compiler configuration. It changes the paradigm from implicit heuristics to explicit async boundaries, enforcing strict compile-time checks to prevent unexpected database hits or layout shifts.


1. What is dynamicIO?

When dynamicIO is enabled, the Next.js compiler enforces a strict rule: any data fetching or dynamic property lookup must be wrapped inside an explicit asynchronous boundary.

If you attempt to read cookies, headers, or query parameters synchronously during a static build phase, the compiler will throw an error rather than silently degrading the page to dynamic rendering.

Enforcing the Flag

To enable this feature, configure it in your next.config.js:

// next.config.js
const nextConfig = {
  experimental: {
    dynamicIO: true,
  },
};

module.exports = nextConfig;

2. The Asynchronous Shift in Server Components

To accommodate dynamicIO, APIs that used to be synchronous are now asynchronous, returning promises that must be resolved.

A. Dynamic Parameters (params & searchParams)

In Next.js 15/16, the parameters passed to layouts and pages are promises.

// ❌ WRONG (fails with dynamicIO enabled):
export default function Page({ params }: { params: { id: string } }) {
  return <div>Item: {params.id}</div>;
}
//  CORRECT: Resolve the parameters asynchronously
export default async function Page({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const resolvedParams = await params;
  return <div>Item: {resolvedParams.id}</div>;
}

B. Headers and Cookies

Reading headers or cookies inside a server component must be executed asynchronously.

//  CORRECT: Asynchronous cookies parsing
import { cookies } from "next/headers";

export default async function SettingsCard() {
  const cookieStore = await cookies();
  const theme = cookieStore.get("theme")?.value || "light";

  return <div class={`settings-box ${theme}`}>Active Theme: {theme}</div>;
}

3. Resolving Dynamic Data with the use() Hook

What if you need to fetch data inside a component that isn't asynchronous? React 19 introduces the use() hook, which allows you to read the value of a promise directly inside synchronous rendering paths (including client components).

// components/user-profile-card.tsx
"use client";

import { use } from "react";

interface UserProfileProps {
  userPromise: Promise<{ name: string; email: string }>;
}

export function UserProfileCard({ userPromise }: UserProfileProps) {
  // Unpacks the promise on the client. 
  // Suspends automatically if the promise is still pending!
  const user = use(userPromise);

  return (
    <div class="user-card">
      <h4>{user.name}</h4>
      <p>{user.email}</p>
    </div>
  );
}

4. How dynamicIO Optimizes Partial Prerendering (PPR)

The ultimate goal of dynamicIO is to make Partial Prerendering (PPR) predictable.

With PPR, Next.js compiles a static HTML page shell (including headers, navigation bars, and layout skeletons) during build time. The dynamic parts (like user profiles or carts) are marked using React Suspense and streamed as HTML chunks as soon as the server resolves them.

┌────────────────────────────────────────────────────────┐
│               PARTIAL PRERENDERING SHELL               │
└────────────────────────────────────────────────────────┘
│  [Static Navbar Component]                             │  <-- Compiled at build time
│  ────────────────────────────────────────────────────  │
│  [Suspense Boundary]                                   │
│    ├── Loading Skeleton UI...                          │  <-- Rendered instantly
│    └── [Dynamic User Profile]                          │  <-- Streamed later at runtime
└────────────────────────────────────────────────────────┘

Without dynamicIO, a developer could write an un-suspended, synchronous cookies() call inside the navbar, which would instantly force the entire page shell to become dynamic, nullifying the performance advantages of PPR.

By enforcing compile-time errors for synchronous dynamic operations, dynamicIO guarantees that your static layouts compile cleanly and remain 100% cached at the CDN Edge.