- Published on
Next.js 15 - Every New Feature Explained
- Authors

- Name
- Sanjeev Sharma
- @webcoderspeed1
Introduction
Next.js 15 brought major improvements across the board — smarter caching, async APIs, full Turbopack stability, React 19 support, and powerful new security features.
This guide covers every significant change with code examples.
- 1. Caching Changes — Fetch No Longer Cached by Default
- 2. Async Request APIs
- 3. Turbopack Stable
- 4. React 19 Support
- 5. Partial Prerendering (PPR) — Stable
- 6. New Security: allowedDevOrigins
- 7. Improved next/form Component
- 8. Instrumentation API — Stable
- Migrating from Next.js 14
- Conclusion
1. Caching Changes — Fetch No Longer Cached by Default
This is the biggest breaking change in Next.js 15.
In Next.js 14, fetch() was cached by default. In Next.js 15, it's not:
// Next.js 14 — cached by default (ISR behavior)
const data = await fetch('https://api.example.com/data')
// Next.js 15 — NOT cached by default (SSR behavior)
const data = await fetch('https://api.example.com/data')
// To opt INTO caching in Next.js 15:
const data = await fetch('https://api.example.com/data', {
cache: 'force-cache' // Explicit opt-in
})
// Or use revalidate for ISR:
const data = await fetch('https://api.example.com/data', {
next: { revalidate: 3600 } // Revalidate every hour
})
This change makes behavior more predictable and easier to reason about.
2. Async Request APIs
In Next.js 15, cookies(), headers(), and params are now async:
// Next.js 14 — synchronous
import { cookies, headers } from 'next/headers'
export default function Page() {
const cookieStore = cookies() // Was sync
const headersList = headers() // Was sync
const token = cookieStore.get('token')
}
// Next.js 15 — async (breaking change!)
export default async function Page({
params,
searchParams,
}: {
params: Promise<{ id: string }>
searchParams: Promise<{ query: string }>
}) {
const { id } = await params // Must await!
const { query } = await searchParams // Must await!
const cookieStore = await cookies() // Must await!
const headersList = await headers() // Must await!
const token = cookieStore.get('token')
return <div>ID: {id}, Query: {query}</div>
}
3. Turbopack Stable
Turbopack (Next.js's Rust-based bundler) is now stable for development in Next.js 15:
{
"scripts": {
"dev": "next dev --turbopack" // Enable Turbopack
}
}
Performance improvements:
- Up to 76% faster local server startup
- Up to 96% faster Fast Refresh
- Significantly faster initial page compilation
Turbopack is now the recommended default for development.
4. React 19 Support
Next.js 15 fully supports React 19, which brings:
// React 19: use() hook — read promises and context
import { use } from 'react'
function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
const user = use(userPromise) // Suspends until resolved
return <div>{user.name}</div>
}
// React 19: New form hooks
import { useActionState } from 'react'
function ContactForm() {
const [state, action, isPending] = useActionState(submitForm, null)
return (
<form action={action}>
<input name="message" />
<button disabled={isPending}>
{isPending ? 'Sending...' : 'Send'}
</button>
{state?.error && <p>{state.error}</p>}
</form>
)
}
5. Partial Prerendering (PPR) — Stable
Partial Prerendering renders the static shell instantly, then streams in dynamic content:
const nextConfig = {
experimental: {
ppr: 'incremental', // Enable PPR per route
},
}
import { Suspense } from 'react'
// Enable PPR for this route
export const experimental_ppr = true
// Static shell renders instantly
export default function ProductPage({ params }) {
return (
<main>
<h1>Our Products</h1> {/* Static — renders at build time */}
{/* Dynamic content — streams in after */}
<Suspense fallback={<ProductSkeleton />}>
<ProductDetails id={params.id} />
</Suspense>
<Suspense fallback={<ReviewsSkeleton />}>
<ProductReviews id={params.id} />
</Suspense>
</main>
)
}
The static shell is served from the CDN instantly. Dynamic parts stream in progressively. Best of both worlds!
6. New Security: allowedDevOrigins
const nextConfig = {
experimental: {
allowedDevOrigins: [
'http://localhost:3001',
'https://my-preview.vercel.app',
],
},
}
7. Improved next/form Component
The new <Form> component from next/form adds client-side navigation to forms:
import Form from 'next/form'
export default function SearchPage() {
return (
<Form action="/search">
{/* Prefetches the /search page on focus */}
{/* Navigation happens without full page reload */}
<input name="query" placeholder="Search..." />
<button type="submit">Search</button>
</Form>
)
}
8. Instrumentation API — Stable
The instrumentation.ts file runs code when your server starts — perfect for observability setup:
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
// Set up OpenTelemetry, Sentry, etc.
const { NodeSDK } = await import('@opentelemetry/sdk-node')
const sdk = new NodeSDK({ /* config */ })
sdk.start()
}
}
export async function onRequestError(
err: Error,
request: Request,
context: { routeType: 'render' | 'route' | 'action' }
) {
// Called for every unhandled request error
await Sentry.captureException(err)
}
Migrating from Next.js 14
The main things to update:
# Upgrade
npm install next@latest react@latest react-dom@latest
# Run the codemod for most breaking changes
npx @next/codemod@canary upgrade latest
Key migration checklist:
- Await
params,searchParams,cookies(),headers() - Update
fetch()calls that relied on implicit caching - Test all dynamic routes with the new async params
Conclusion
Next.js 15 is a significant release that makes the framework more predictable (explicit caching), faster (Turbopack stable, PPR), and more powerful (React 19, instrumentation). The caching change is the most impactful for existing apps, but the codemod handles most of the migration automatically. Upgrade when ready — the improvements are well worth it.