Next.js 15 landed with a set of changes that meaningfully shift how you think about data fetching, caching, and bundling. If you're upgrading from Next.js 14 or earlier, this guide covers what changed and how to migrate without breaking your app.
The Big Changes at a Glance
- Turbopack is now stable for development — significantly faster HMR
- Caching defaults flipped — fetch requests are no longer cached by default
- Partial Prerendering (PPR) is available as an opt-in feature
- React 19 support — including the new
use()hook and Server Actions improvements after()API — run code after a response has been sent
Turbopack for Development
Turbopack replaces Webpack as the default dev bundler. You don't need to do anything — running next dev now uses Turbopack automatically. The improvement is dramatic on large codebases: cold starts that took 8–10 seconds now take under 2.
# Next.js 15 — Turbopack is the default
next dev
# If you need to opt out for any reason
next dev --no-turbopack
Caching Defaults Have Changed
This is the most breaking change. In Next.js 14, fetch() calls in Server Components were cached by default. In Next.js 15, they are not cached by default. This means your pages will be dynamic unless you explicitly opt into caching.
// Next.js 14 — cached by default (force-cache)
const data = await fetch('https://api.example.com/data')
// Next.js 15 — NOT cached by default (no-store)
const data = await fetch('https://api.example.com/data')
// To cache in Next.js 15, be explicit:
const data = await fetch('https://api.example.com/data', {
cache: 'force-cache',
// or use revalidation:
next: { revalidate: 3600 }
})
Partial Prerendering (PPR)
PPR is the most exciting architectural addition. It lets you render a static shell of a page at build time, then stream in dynamic parts at request time — all from a single route, with no extra configuration per component.
Enable it in next.config.mjs:
const nextConfig = {
experimental: {
ppr: 'incremental',
},
}
export default nextConfig
Then opt individual routes in:
// app/dashboard/page.tsx
export const experimental_ppr = true
export default function Dashboard() {
return (
<div>
<StaticHeader /> {/* rendered at build time */}
<Suspense fallback={<Skeleton />}>
<DynamicFeed /> {/* streamed at request time */}
</Suspense>
</div>
)
}
The new after() API
Sometimes you need to do work after a response is sent — logging, analytics, cache warming — without blocking the user. The new after() function handles exactly this:
import { after } from 'next/server'
export async function POST(request) {
const data = await request.json()
const result = await saveToDatabase(data)
// This runs after the response is sent
after(async () => {
await logAnalyticsEvent('form_submitted', data)
})
return Response.json(result)
}
Migration Checklist
- Run
npx @next/codemod@canary upgrade latestto auto-migrate most breaking changes - Audit all
fetch()calls — add explicitcacheoptions where needed - Check
cookies(),headers(), andparams— they're now async in Next.js 15 - Update
next.config.jstonext.config.mjsif you haven't already - Test your app with Turbopack and report any issues to the Next.js team
Wrapping Up
Next.js 15 is a solid release. The caching change is the most disruptive but also the most honest — explicit is better than implicit. PPR is genuinely exciting and worth experimenting with on your next project.