Next.js 15 Complete Guide: Turbopack, React 19, Async APIs & Migration

Next.js 15 ships Turbopack as stable, full React 19 support, and Partial Pre-Rendering - here is what changed and how to migrate from v14.

Mahmudul Haque Qudrati — CEO & ML Engineer at Pristren

Mahmudul Haque Qudrati

CEO & ML Engineer

March 6, 2026
9 min read
Next.js 15 Complete Guide: Turbopack, React 19, Async APIs & Migration

Next.js 15 Is Here: What Actually Changed?

Next.js 15 represents one of the most consequential architectural transitions in the React ecosystem. Rather than introducing experimental gimmicks, this release stabilizes core performance foundations that teams have anticipated for over a year: Turbopack as the default dev bundler, React 19 support by default, Partial Pre-Rendering (PPR) for hybrid static/dynamic delivery, and a complete overhaul of request-time caching defaults.

For engineering teams maintaining production applications or building modern web applications, Next.js 15 requires deliberate architectural adjustments - most notably around async request APIs and uncached fetch calls.

In this guide, we break down every major feature, provide concrete migration code patterns, explore performance benchmarks, and walk through real-world production gotchas.


1. Turbopack Is Stable for Development

After extensive community testing across thousands of repositories, Turbopack is officially stable and enabled by default for local development via next dev.

Performance Gains in Practice

According to official benchmarks and real-world monorepo testing:

  • Up to 76.7% faster local server startup compared to Webpack.
  • Up to 96.3% faster Fast Refresh (HMR) on large enterprise codebases with thousands of modules.
  • Substantially reduced idle memory footprint during sustained development sessions.

You no longer need to pass --turbopack in your package.json scripts:

bash
# Automatically boots with Turbopack in Next.js 15
pnpm dev

# Build output confirms:
#  ▲ Next.js 15.x.x (Turbopack)
#  - Local:        http://localhost:3000
#  ✓ Starting...
#  ✓ Ready in 380ms

Handling Custom Webpack Plugins

If your project relies on legacy custom Webpack loaders (such as specific SVG transformers, raw GLSL shaders, or custom Webpack hooks), Turbopack will alert you. You can temporarily fall back to Webpack using:

bash
next dev --turbopack=false

However, most modern workflows - including Tailwind CSS, PostCSS, SVGR, and path aliases - work out of the box with zero configuration.


2. React 19 Integration & New Component Primitives

Next.js 15 adopts React 19 as its core rendering engine. This brings several long-awaited React primitives into standard App Router workflows:

Stable Server Actions & useActionState

Server Actions are now fully stabilized. The legacy useFormState hook from react-dom has been replaced by React 19's native useActionState, which natively manages pending states and server response payloads without extra state hooks:

tsx
// app/actions.ts
"use server";

import { z } from "zod";

const TaskSchema = z.object({
  title: z.string().min(3, "Title must have at least 3 characters"),
});

export type ActionState = {
  success?: boolean;
  errors?: { title?: string[] };
};

export async function createTaskAction(
  prevState: ActionState,
  formData: FormData
): Promise<ActionState> {
  const result = TaskSchema.safeParse({
    title: formData.get("title"),
  });

  if (!result.success) {
    return { errors: result.error.flatten().fieldErrors };
  }

  // Persist to database
  return { success: true };
}
tsx
// app/components/TaskForm.tsx
"use client";

import { useActionState } from "react";
import { createTaskAction, ActionState } from "@/app/actions";

const initialState: ActionState = {};

export function TaskForm() {
  const [state, formAction, isPending] = useActionState(createTaskAction, initialState);

  return (
    <form action={formAction} className="space-y-4 max-w-md">
      <input
        type="text"
        name="title"
        placeholder="Enter task title"
        className="w-full px-3 py-2 border rounded"
        disabled={isPending}
      />
      {state.errors?.title && (
        <p className="text-red-500 text-sm">{state.errors.title[0]}</p>
      )}
      <button
        type="submit"
        disabled={isPending}
        className="px-4 py-2 bg-slate-900 text-white rounded disabled:opacity-50"
      >
        {isPending ? "Saving..." : "Create Task"}
      </button>
    </form>
  );
}

Reading Promises with the use() Hook

The React 19 use() API allows client components to read promises directly in the render phase when coupled with Suspense boundaries, eliminating boilerplate useEffect data-fetching patterns:

tsx
"use client";

import { use, Suspense } from "react";

function UserProfile({ userPromise }: { userPromise: Promise<{ name: string; email: string }> }) {
  const user = use(userPromise);
  return (
    <div className="p-4 border rounded">
      <h3 className="font-bold">{user.name}</h3>
      <p className="text-sm text-gray-600">{user.email}</p>
    </div>
  );
}

export function UserSection({ userPromise }: { userPromise: Promise<{ name: string; email: string }> }) {
  return (
    <Suspense fallback={<p>Loading user profile...</p>}>
      <UserProfile userPromise={userPromise} />
    </Suspense>
  );
}

3. The Critical Breaking Change: Async Request APIs

The most widespread breaking change in Next.js 15 is that synchronous request data is now asynchronous.

In Next.js 14 and earlier, request-specific context like cookies(), headers(), and page route params / searchParams were read synchronously. In Next.js 15, to prepare the runtime for upcoming server optimizations like streaming and PPR, these APIs return Promises.

Before vs After: Route Parameters (params)

In Next.js 14:

tsx
// Next.js 14 (DEPRECATED)
interface PageProps {
  params: { slug: string };
  searchParams: { page?: string };
}

export default function BlogPostPage({ params, searchParams }: PageProps) {
  const slug = params.slug; // Synchronous access
  const page = searchParams.page;
  return <div>Post: {slug}</div>;
}

In Next.js 15:

tsx
// Next.js 15 (REQUIRED)
interface PageProps {
  params: Promise<{ slug: string }>;
  searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
}

export default async function BlogPostPage({ params, searchParams }: PageProps) {
  const { slug } = await params; // Must await params
  const { page } = await searchParams; // Must await searchParams

  return <div>Post: {slug} (Page: {page})</div>;
}

Before vs After: cookies() and headers()

tsx
// Next.js 14:
import { cookies, headers } from "next/headers";

export async function ServerComponent() {
  const cookieStore = cookies();
  const token = cookieStore.get("session_token");
}

// Next.js 15:
import { cookies, headers } from "next/headers";

export async function ServerComponent() {
  const cookieStore = await cookies(); // Must be awaited
  const headerList = await headers();  // Must be awaited
  const token = cookieStore.get("session_token");
}

Pro Tip: Vercel provides an automated codemod to rewrite these signatures across your codebase:

bash
npx @next/codemod@canary next-async-request-api .

4. Caching Overhaul: fetch Is No Longer Cached by Default

In Next.js 13 and 14, one of the most controversial features was aggressive automatic caching on all fetch() requests (force-cache was the default). Developers often struggled with stale data during production deployments.

Next.js 15 reverses this behavior:

  • fetch() requests are now uncached by default (cache: "no-store").
  • GET Route Handlers are now uncached by default.
  • Client navigation caching (Router Cache) page staling time is now 0 seconds by default for dynamic pages.
typescript
// In Next.js 14: This was cached by default indefinitely.
// In Next.js 15: This executes dynamically on every request.
const res = await fetch("https://api.example.com/products");

// If you WANT caching in Next.js 15, you must be explicit:
const cachedRes = await fetch("https://api.example.com/products", {
  cache: "force-cache",
  next: { revalidate: 3600 }, // Revalidate every hour
});

For database queries and ORMs (Prisma, Drizzle, Mongoose) that do not use fetch, continue using unstable_cache to achieve granular ISR caching.


5. Partial Pre-Rendering (PPR)

Partial Pre-Rendering (PPR) combines static and dynamic rendering in a single HTTP response.

Traditionally, you had to choose between SSG (fast static HTML at edge CDN, but no personalized user data) or SSR (dynamic data on every request, but slower initial TTFB). With PPR:

  1. Next.js serves a static shell instantly from the nearest edge cache.
  2. Dynamic holes wrapped in React <Suspense> stream in asynchronously over the same open connection.

To enable experimental PPR in next.config.ts:

typescript
// next.config.ts
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  experimental: {
    ppr: "incremental", // Allows opting in per-page or layout
  },
};

export default nextConfig;

Then, on any page or layout, export the PPR flag:

tsx
// app/dashboard/page.tsx
import { Suspense } from "react";
import { UserGreeting } from "./UserGreeting";
import { StaticSidebar } from "./StaticSidebar";

export const experimental_ppr = true;

export default function Dashboard() {
  return (
    <div className="flex min-h-screen">
      {/* Static Shell: Served immediately from CDN */}
      <StaticSidebar />

      <main className="flex-1 p-8">
        <h1 className="text-2xl font-bold">Dashboard</h1>

        {/* Dynamic Hole: Streams in as soon as user DB query finishes */}
        <Suspense fallback={<div className="h-20 bg-gray-100 animate-pulse rounded" />}>
          <UserGreeting />
        </Suspense>
      </main>
    </div>
  );
}

6. Background Work with the after() API

A frequent architectural challenge in serverless environments is performing secondary tasks - such as analytics logging, audit trails, and cache warming - without blocking the client response.

Next.js 15 introduces after(), which executes tasks after the response has been closed and flushed to the user:

typescript
import { after } from "next/server";
import { logAnalyticsEvent } from "@/lib/analytics";

export async function POST(request: Request) {
  const body = await request.json();

  // 1. Process critical user action
  const order = await processOrder(body);

  // 2. Schedule non-blocking background telemetry
  after(async () => {
    await logAnalyticsEvent({
      event: "order_created",
      orderId: order.id,
      timestamp: Date.now(),
    });
  });

  // 3. Client receives response immediately
  return Response.json({ success: true, orderId: order.id });
}

This pattern dramatically improves Interaction to Next Paint (INP) and user-perceived latency.


7. Migration Checklist: Moving from Next.js 14 to 15

If you are upgrading an existing production application, follow this systematic upgrade sequence:

Step 1: Upgrade Dependencies

bash
pnpm add next@latest react@latest react-dom@latest
pnpm add -D @types/react@latest @types/react-dom@latest eslint-config-next@latest

Step 2: Run the Official Codemod

bash
npx @next/codemod@canary upgrade latest

Step 3: Audit Async Parameters

Search for all route handlers, layouts, and page components that use params, searchParams, cookies(), or headers(). Ensure they are marked async and awaited.

Step 4: Verify Fetch Caching Requirements

Review external API calls. If your application relies on cached responses (e.g. headless CMS posts, exchange rates, site settings), add { cache: "force-cache" } or next: { revalidate: seconds }.

Step 5: Check React 18 Compatibility Flags

If an older third-party package has strict peerDependencies specifying React 18:

bash
# In pnpm (.npmrc or command line):
pnpm install --no-frozen-lockfile

Next.js 15 in Enterprise Architecture

Next.js 15 solidifies the App Router as an enterprise-grade standard for performant web systems. By pairing Turbopack with React 19 and Partial Pre-Rendering, applications achieve both lightning-fast development iterations and sub-second real-world page loads.

If you are designing high-performance digital platforms or modernizing legacy infrastructure, explore our work in building scalable enterprise web applications and browse our latest engineering insights across the Web Development category and our shipped case studies.

Official References & Further Reading

Frequently Asked Questions

Is Next.js 15 backward compatible with Next.js 14?

Next.js 15 includes breaking changes that require code updates. The most notable are asynchronous request APIs (cookies, headers, params, searchParams) and the removal of default fetch request caching. Vercel provides automated codemods to assist with migration.

Why did Next.js 15 make params and searchParams asynchronous?

Making params and searchParams asynchronous allows the Next.js server runtime to begin rendering static page shells immediately before dynamic request data is resolved, enabling features like Partial Pre-Rendering (PPR) and concurrent streaming.

Does Turbopack in Next.js 15 support all custom Webpack plugins?

Turbopack natively supports standard web assets, PostCSS, and Tailwind CSS. However, bespoke Webpack plugins and custom loaders may still require Webpack. You can opt out of Turbopack using 'next dev --turbopack=false' if legacy plugins are required.

How does Partial Pre-Rendering (PPR) differ from ISR in Next.js 15?

ISR generates and caches the entire page periodically. Partial Pre-Rendering (PPR) allows a single page to be simultaneously static (the layout/shell served instantly from the CDN) and dynamic (interactive holes streaming personalized user data over the same connection).

#next.js-15#react-19#turbopack#app-router#web-development

One AI engineering post, weekly

LLM benchmarks, prompt techniques, and token-cost breakdowns, delivered to your inbox.

Comments

Have a question or something to add? Sign in to join the discussion.

Mahmudul Haque Qudrati — CEO & ML Engineer at Pristren

Mahmudul Haque Qudrati

CEO & ML Engineer

Visionary technologist, software engineer, and machine learning specialist. Founder and CEO of Pristren, directing engineering teams that ship production-grade AI/ML pipelines, mission-critical full-stack applications, and developer tooling. Creator of Zlyqor, the unified team workspace platform. Author of 540+ technical guides and benchmark research reports on large language models, agentic workflows, Model Context Protocol (MCP), and modern web stacks.

PristrenZlyqor