Next.js 15 represents a major leap in the React meta-framework ecosystem, with Turbopack now stable for development, a completely reworked caching model, and Partial Prerendering bringing the best of static and dynamic rendering together.
🚀 What's New in Next.js 15
- Turbopack (Stable) — Up to 76% faster local dev startup and 96% faster HMR
- Partial Prerendering (PPR) — Mix static and dynamic content in a single route
- Improved Caching — fetch requests and GET route handlers are no longer cached by default
- React 19 Support — Full integration with React 19 features
- next/after — Execute code after a response finishes streaming
1. Turbopack — Blazing Fast Development
Turbopack is now the default dev bundler. No config changes needed — just run next dev and enjoy the speed.
# Turbopack is now default in Next.js 15
next dev
# Benchmarks (large app with 5000+ modules):
# Cold start: 3.4s → 0.8s (76% faster)
# HMR: 350ms → 15ms (96% faster)
# Route compilation: 1.2s → 0.2s
2. Partial Prerendering (PPR)
PPR allows a single route to have both static and dynamic parts. The static shell is served instantly from the CDN, while dynamic content streams in via Suspense.
// app/product/[id]/page.tsx
import { Suspense } from 'react';
// Enable PPR for this route
export const experimental_ppr = true;
// This part is static (prerendered at build time)
export default async function ProductPage({ params }) {
const product = await getProduct(params.id);
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
{/* This part is dynamic (streamed on each request) */}
<Suspense fallback={<PriceSkeleton />}>
<LivePrice productId={params.id} />
</Suspense>
<Suspense fallback={<ReviewsSkeleton />}>
<CustomerReviews productId={params.id} />
</Suspense>
</div>
);
}
3. Enhanced Server Actions
Server Actions in Next.js 15 have improved error handling, better TypeScript inference, and support for revalidation.
// app/actions.ts
'use server';
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
export async function createPost(formData: FormData) {
const title = formData.get('title') as string;
const content = formData.get('content') as string;
// Validate
if (!title || title.length < 3) {
return { error: 'Title must be at least 3 characters' };
}
// Insert into database
const post = await db.post.create({
data: { title, content, publishedAt: new Date() }
});
// Revalidate the blog listing page
revalidatePath('/blog');
// Redirect to the new post
redirect(`/blog/${post.slug}`);
}
// app/new-post/page.tsx
import { createPost } from './actions';
export default function NewPostPage() {
return (
<form action={createPost}>
<input name="title" placeholder="Post title" required />
<textarea name="content" placeholder="Write your post..." />
<button type="submit">Publish</button>
</form>
);
}
4. next/after — Post-Response Tasks
Run code after the response has been sent to the user — perfect for logging, analytics, and cache warming.
import { after } from 'next/server';
export async function POST(request: Request) {
const data = await request.json();
const result = await processOrder(data);
// This runs AFTER the response is sent
after(async () => {
await sendConfirmationEmail(data.email);
await updateAnalytics('order_placed', data);
await warmCache(`/orders/${result.id}`);
});
return Response.json(result);
}
5. Caching Changes
Next.js 15 no longer caches fetch requests by default — you must explicitly opt in. This makes the behaviour more predictable.
// Next.js 14 — cached by default
fetch('https://api.example.com/data');
// Next.js 15 — NOT cached by default
fetch('https://api.example.com/data');
// Explicitly opt into caching
fetch('https://api.example.com/data', {
cache: 'force-cache' // Cache indefinitely
});
fetch('https://api.example.com/data', {
next: { revalidate: 60 } // Revalidate every 60 seconds
});