Introduction
Next.js 15 ships as a stable release with React 19 support, a redesigned caching model, and major API improvements for async request handling. This version defaults to no caching for fetch requests and GET Route handlers, simplifying behavior predictability. It also introduces async variants for cookies(), headers(), and params(), aligning with React 19’s async rendering paradigm. This article covers every breaking change and best practice for production adoption.
React 19 Stable Compatibility
Next.js 15 pairs with React 19 stable, enabling the new React Compiler to automatically memoize components and hooks. This eliminates the need for manual useMemo, useCallback, and React.memo in many cases.
To opt into the React Compiler:
// next.config.ts
const nextConfig = {
experimental: {
reactCompiler: true,
},
};
export default nextConfig;
New React 19 APIs like use() (reading promises directly in render) and Server Actions with "use server" are fully supported.
Caching Defaults Changed
In Next.js 14, fetch requests were cached by default. In Next.js 15, fetch requests are not cached by default — you must explicitly opt in:
// Next.js 15 — NOT cached unless specified
const data = await fetch('https://api.example.com/data');
// Opt into caching
const cached = await fetch('https://api.example.com/data', {
cache: 'force-cache',
});
GET Route Handlers also return dynamic responses by default. To make them static:
// app/api/data/route.ts
export const dynamic = 'force-static';
This change reduces surprise behavior in dynamic applications and aligns with the principle of progressive enhancement.
Async Request APIs: cookies(), headers(), params()
Next.js 15 requires async access for request-time APIs:
// app/dashboard/page.tsx
import { cookies } from 'next/headers';
export default async function Page() {
const cookieStore = await cookies();
const token = cookieStore.get('session');
// ...
}
The same applies to headers():
import { headers } from 'next/headers';
export default async function Page() {
const headersList = await headers();
const userAgent = headersList.get('user-agent');
}
And dynamic route params are now async:
// app/blog/[slug]/page.tsx
export default async function Page({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
return <div>Post: {slug}</div>;
}
This change enables better streaming and suspense integration.
Turbopack Improvements
Turbopack, the Rust-based bundler, reaches stable for development in Next.js 15. Key improvements include:
- 70% faster cold starts compared to webpack
- Instant Fast Refresh with persistent caching
- Improved CSS and SVG handling without plugins
Enable it explicitly:
// next.config.ts
const nextConfig = {
turbopack: true,
};
Other Notable Changes
| Feature | Next.js 14 | Next.js 15 |
|---|---|---|
| React version | React 18 | React 19 + Compiler |
| fetch caching | Default cached | Default uncached |
params access | Synchronous | Promise-based |
cookies()/headers() | Synchronous | async functions |
| Dev bundler | webpack (default) | Turbopack (default) |
| Forms | Server Actions | Enhanced "use server" directives |
Migration from Next.js 14
- Update packages:
npm install next@15 react@19 react-dom@19 - Make
paramsasync: Change anyparamsusage toawait params - Update
cookies()/headers(): Addawaitbefore each call - Review caching: Add
cache: 'force-cache'where caching is needed - Enable Turbopack: Set
turbopack: trueinnext.config.ts - Test React Compiler: Enable
reactCompiler: trueexperimentally
Conclusion
Next.js 15 brings long-awaited stability with React 19, predictable caching defaults, and cleaner async request APIs. The move to Turbopack as the default dev bundler significantly improves iteration speed. While the caching default change may require adding explicit opt-ins, the overall result is a more transparent and performant framework. Teams should plan for the async API migrations but will benefit from reduced configuration overhead and future-proof React 19 integration.
