Next.js 15 Is Here
Next.js 15 is a significant release that stabilizes features that have been experimental for over a year. The three headline changes are Turbopack going stable, React 19 becoming the default, and Partial Pre-Rendering landing as an opt-in production feature.
Turbopack Is Now Stable
After months in beta, Turbopack is the default bundler for next dev. The numbers are real: up to 76.7% faster local server startup compared to webpack, and up to 96.3% faster Fast Refresh on large applications.
You do not need to change anything. Run next dev and Turbopack is already active. If you hit an incompatible webpack plugin (rare but possible), opt back out with next dev --turbopack=false.
# Already works — no flags needed
pnpm dev
# Check your build output for Turbopack confirmation
# ✓ Starting...
# ✓ Ready in 432ms ← Turbopack
React 19 Integration
Next.js 15 requires React 19. The two biggest APIs you will use immediately are:
Server Actions are now stable. You can define async functions in Server Components and pass them as props:
// app/actions.ts
"use server";
export async function createTask(formData: FormData) {
const title = formData.get("title") as string;
await db.tasks.insert({ title });
}
// app/page.tsx
import { createTask } from "./actions";
export default function Page() {
return (
<form action={createTask}>
<input name="title" />
<button type="submit">Add Task</button>
</form>
);
}
The use() hook lets you read promises and context inside render, replacing many useEffect patterns:
import { use } from "react";
function UserName({ userPromise }: { userPromise: Promise<User> }) {
const user = use(userPromise); // suspends until resolved
return <span>{user.name}</span>;
}
Partial Pre-Rendering (PPR)
PPR lets a single route be partly static and partly dynamic — without client-side JavaScript for the static shell. Enable it per-layout or globally:
// next.config.ts
export default {
experimental: {
ppr: true,
},
};
The static shell is served from the CDN immediately. Dynamic holes (wrapped in <Suspense>) stream in after. This gives you the performance of a static site with the flexibility of a dynamic app.
The after() API
Run work after a response has already been sent to the user — perfect for logging, analytics, and non-critical side effects:
import { after } from "next/server";
export async function GET() {
after(() => {
// runs after response is flushed
analytics.track("api_called");
});
return Response.json({ ok: true });
}
Caching Changes
This is the breaking change most teams hit during migration: fetch requests are no longer cached by default. In Next.js 14, every fetch was cached unless you opted out. In v15, you must opt in:
// v14 default (cached)
fetch("/api/data");
// v15 — must be explicit
fetch("/api/data", { cache: "force-cache" }); // cached
fetch("/api/data", { cache: "no-store" }); // no cache (now default)
Run the automated codemod to catch most of these:
npx @next/codemod@canary upgrade latest
instrumentation.ts for Observability
A new top-level file for hooking into the Next.js lifecycle — register OpenTelemetry, Sentry, or custom tracing:
// instrumentation.ts
export async function register() {
if (process.env.NEXT_RUNTIME === "nodejs") {
const { init } = await import("./lib/tracing");
init();
}
}
Migration Notes From v14
- Run
pnpm add next@latest react@latest react-dom@latest - Run the codemod:
npx @next/codemod@canary upgrade latest - Audit all bare
fetch()calls — add{ cache: "force-cache" }where you relied on automatic caching - Update
useFormState→useActionState(React 19 rename) - Remove
forwardRefwrappers — refs now work as regular props
References: Next.js 15 blog · upgrade guide · Turbopack