Next.js 16: what's new?

Next.js 16 is a major release that includes several new features and improvements. In this article, we will cover the new features and improvements in Next.js 16.

Next.js 16 shipped on October 21, 2025, and it's the largest performance-focused release since the App Router launched. Build times dropped by 50% or more with the now-stable Turbopack bundler, and the new caching model gives you explicit control over what gets cached.

Next.js 16 is the October 2025 major release of the React framework. It makes Turbopack the default bundler (2-5x faster builds), introduces Cache Components with "use cache" directives replacing PPR, and bundles React 19.2. The upgrade from Next.js 15 is straightforward because most breaking changes are handled by codemods.

What you get in Next.js 16:

  • Turbopack is stable and enabled by default (2-5x faster builds, up to 10x faster Fast Refresh)
  • Cache Components replace PPR with explicit "use cache" directives
  • New caching APIs: updateTag(), refresh(), and an improved revalidateTag()
  • proxy.ts replaces middleware for clearer network boundary control
  • React 19.2 with <Activity>, useEffectEvent, and View Transitions
  • React Compiler is now stable (still opt-in)
  • DevTools MCP for AI-assisted debugging

Tested with Next.js 16.1 and React 19.2. Makerkit's Next.js SaaS kits ship with these defaults.

Should You Upgrade to Next.js 16?

Upgrade now if:

  • You're starting a new project (Next.js 16 is the new default)
  • Build times are a bottleneck for your team
  • You want explicit control over caching with Cache Components
  • You're already on Next.js 15 and have async dynamic APIs

Wait if:

  • You depend on OpenNext and can't work around the proxy.ts limitation
  • Your project uses AMP (all AMP APIs are removed)
  • Third-party libraries you depend on aren't React 19.2 compatible yet

If unsure: Upgrade a staging environment first and run your test suite. Most projects migrate without issues.

Turbopack Is Now Stable and Default

Turbopack, the Rust-based bundler that replaces Webpack, graduates from experimental to stable in Next.js 16. It's enabled by default for both development and production builds.

The performance gains are substantial. In our testing with the Makerkit SaaS Kit:

  • Development startup: 603ms (down from 1083ms in Next.js 15)
  • Production builds: 5.7 seconds with Turbopack vs 24.5 seconds with Webpack
  • Fast Refresh: Under 100ms for most changes

The Makerkit SaaS Kit now starts in 603ms with Next.js 16, nearly half the time of Next.js 15.

Filesystem Caching (Experimental)

Turbopack now supports filesystem caching, which persists compilation results across dev server restarts. This makes subsequent startups significantly faster.

Enable it in your next.config.ts:

next.config.ts

const nextConfig = {
experimental: {
turbopackFileSystemCacheForDev: true,
turbopackFileSystemCacheForBuild: true,
},
};
export default nextConfig;

This is still experimental. We've seen occasional stale cache issues, so clear .next if you hit strange behavior.

Benchmarks: Next.js 16 vs Next.js 15

We benchmarked Next.js 16 against Next.js 15 using the Makerkit SaaS Kit on a MacBook Pro M3 Max with 36GB RAM.

Startup Time

Every metric improved. Edge instrumentation dropped from 38ms to 4ms. Total ready time dropped 44%.

Most routes are faster. The dynamic /home/[account] route shows a slight regression, which we're investigating. Initial page load dropped from 2.9s to 1.8s.

Production Build Times

Turbopack in Next.js 16 builds the Makerkit SaaS Kit in 5.7 seconds, which is 4.3x faster than Webpack on Next.js 16 and 4.9x faster than Webpack on Next.js 15.

Not quite Vite-level instant, but the gap is closing. For a real SaaS codebase with auth, billing, and database integrations, these numbers are solid.

Cache Components Replace PPR

The experimental ppr flag and dynamicIO are gone. Cache Components are the new model, and they're more explicit about what gets cached.

Instead of configuring PPR globally, you mark individual components or functions with "use cache":

app/posts/page.tsx

import { Suspense } from 'react';
async function BlogPosts() {
'use cache';
const posts = await db.posts.findMany();
return <PostList posts={posts} />;
}
export default function PostsPage() {
return (
<Suspense fallback={<PostsSkeleton />}>
<BlogPosts />
</Suspense>
);
}

The compiler generates cache keys automatically. Dynamic code runs at request time by default. Only "use cache" blocks get cached.

Enable Cache Components in your config:

next.config.ts

const nextConfig = {
cacheComponents: true,
};
export default nextConfig;

This approach is cleaner than PPR's incremental opt-in. You see exactly what's cached by looking at the code.

New Caching APIs

Next.js 16 introduces three caching functions that give you precise control over cache invalidation.

updateTag(): Immediate Cache Refresh

updateTag() expires a cache tag and immediately refreshes the data within the same request. This provides read-your-writes semantics: the user sees their change immediately, not stale data.

app/actions/profile.ts

'use server';
import { updateTag } from 'next/cache';
import { db } from '@/lib/db';
export async function updateUserProfile(userId: string, data: ProfileData) {
await db.users.update(userId, data);
updateTag(`user-${userId}`);
}

Use updateTag() when the user needs to see their change immediately, like updating a profile or posting a comment.

Constraint: updateTag() only works in Server Actions. It can't be called from Route Handlers or anywhere else. Use revalidateTag() in those contexts.

revalidateTag(): Stale-While-Revalidate

revalidateTag() now requires a cache profile as the second argument. This enables stale-while-revalidate behavior: users see cached content immediately while Next.js refreshes in the background.

app/actions/posts.ts

'use server';
import { revalidateTag } from 'next/cache';
export async function publishPost(postId: string) {
await db.posts.publish(postId);
// Use built-in profiles
revalidateTag('blog-posts', 'max');
// Or specify inline
revalidateTag('homepage', { expire: 3600 });
}

Built-in profiles: 'max', 'hours', 'days'. Use revalidateTag() for content where brief staleness is acceptable, like blog posts or product listings.

refresh(): Update Uncached Data

refresh() updates uncached data without touching the cache at all. Use it for real-time indicators that shouldn't be cached.

app/actions/notifications.ts

'use server';
import { refresh } from 'next/cache';
export async function markNotificationRead(id: string) {
await db.notifications.markRead(id);
refresh();
}

This refreshes notification counts, unread indicators, or live metrics without invalidating your cached page shell.

When to Use Each

FunctionUse CaseBehavior
updateTag()User edits their dataBlocks until fresh data arrives
revalidateTag()Content updatesShows stale data, refreshes in background
refresh()Real-time indicatorsUpdates uncached data only

proxy.ts Replaces middleware.ts

The middleware filename is deprecated. Rename it to proxy.ts to clarify that it handles network boundary concerns like rewrites, redirects, and request modification.

Before (Next.js 15):

middleware.ts

import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
if (!request.cookies.get('session')) {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
}

After (Next.js 16):

proxy.ts

import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function proxy(request: NextRequest) {
if (!request.cookies.get('session')) {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/dashboard/:path*', '/settings/:path*'],
};

Key changes:

  • Rename middleware.ts to proxy.ts
  • Rename the exported function from middleware to proxy (or use default export)
  • No more response bodies: Proxy can only rewrite, redirect, or modify headers. Return full responses from Route Handlers instead.

The middleware.ts file still works but is deprecated and will be removed in a future version. Run the codemod to migrate automatically:

npx @next/codemod@canary upgrade latest

The codemod will output: Migrated middleware.ts to proxy.ts and list the updated files.

If you use OpenNext: Check compatibility before migrating. As of this writing, OpenNext doesn't support proxy.ts yet. Keep using middleware.ts until support is added.

React 19.2

Next.js 16 bundles React 19.2 with several new features.

Activity Component

<Activity> controls component visibility while preserving state. Unlike conditional rendering, hidden components keep their state intact.

import { Activity } from 'react';
function TabPanel({ activeTab }: { activeTab: string }) {
return (
<>
<Activity mode={activeTab === 'home' ? 'visible' : 'hidden'}>
<HomePage />
</Activity>
<Activity mode={activeTab === 'settings' ? 'visible' : 'hidden'}>
<SettingsPage />
</Activity>
</>
);
}

Use <Activity> when you want to pre-render content the user will likely visit, or preserve form state when switching tabs.

useEffectEvent Hook

useEffectEvent creates stable callbacks that always access the latest props and state without adding them to the dependency array.

import { useEffect, useEffectEvent } from 'react';
function ChatRoom({ roomId, onMessage }) {
const onMessageEvent = useEffectEvent((message) => {
onMessage(roomId, message);
});
useEffect(() => {
const connection = connect(roomId);
connection.on('message', onMessageEvent);
return () => connection.disconnect();
}, [roomId]); // onMessage not needed in deps
}

This reduces unnecessary effect re-runs and makes dependency arrays honest.

View Transitions

React 19.2 supports the View Transitions API for animating navigation changes. Combined with Next.js's routing, you get smooth page transitions with minimal code.

For a detailed breakdown of React 19.2 changes including the new ESLint rules, see our React 19.2 upgrade guide.

React Compiler Is Stable

The React Compiler is now stable and moved outside the experimental phase. It automatically memoizes components and hooks, reducing unnecessary re-renders.

next.config.ts

const nextConfig = {
reactCompiler: true,
};
export default nextConfig;

Makerkit is fully compatible with the React Compiler. However, we keep it disabled by default because:

  1. Most Makerkit pages are server-rendered, so memoization provides limited benefit
  2. It can introduce performance regressions in some cases
  3. Third-party libraries may not be compatible

Enable it if your app has complex, client-heavy components that re-render frequently. Profile before and after to verify improvements.

Improved Navigation and Prefetching

Next.js 16 rewrote the prefetch cache with two significant optimizations.

Layout Deduplication

Shared layouts are now downloaded once and reused across prefetched links. If ten links share a layout, you download it once instead of ten times.

In our testing, this reduced prefetch data transfer by 60-80% on pages with many navigation links.

Incremental Prefetching

Next.js now only prefetches the parts of a route that aren't already cached. If you have the layout cached, it only fetches the page content.

Combined with automatic prefetch cancellation when links leave the viewport, navigation feels significantly smoother.

DevTools MCP for AI-Assisted Debugging

Next.js 16 introduces a Model Context Protocol (MCP) integration that helps AI coding assistants understand your application.

The DevTools MCP provides:

  • Next.js routing, caching, and rendering knowledge
  • Unified browser and server logs
  • Automatic error access with stack traces
  • Page and route awareness

If you use Claude Code, Cursor, or other AI coding tools, the MCP gives them context about your Next.js app structure, making debugging suggestions more accurate.

Common Pitfalls and How to Fix Them

Stale Turbopack filesystem cache

Symptom: Strange behavior after updating dependencies or changing config.

Fix: Clear the .next directory:

rm -rf .next && npm run dev

updateTag() not working in Route Handlers

Symptom: Cache isn't invalidating when you call updateTag() from a Route Handler.

Fix: updateTag() only works in Server Actions. Use revalidateTag() in Route Handlers instead.

revalidateTag() missing profile argument

Symptom: TypeScript error or runtime error when calling revalidateTag().

Fix: Add the required cache profile as the second argument:

// Before (Next.js 15)
revalidateTag('posts');
// After (Next.js 16)
revalidateTag('posts', 'max');

proxy.ts not supported by your hosting provider

Symptom: Deployment fails or middleware doesn't run.

Fix: If you're using OpenNext or a provider that doesn't support proxy.ts yet, keep using middleware.ts. It's deprecated but still works.

Dynamic route regressions

Symptom: Some dynamic routes are slower than in Next.js 15.

Fix: Profile the specific route. Check if you're using uncached data fetching where caching would help. We observed this with /home/[account] and are investigating.

React Compiler breaks third-party libraries

Symptom: Runtime errors or unexpected behavior after enabling React Compiler.

Fix: Disable React Compiler and identify the problematic library. You can exclude specific files from compilation or wait for library updates.

Breaking Changes to Watch For

Minimum Version Requirements

  • Node.js 20.9+ (Node 18 is no longer supported)
  • TypeScript 5.1+
  • Modern browsers: Chrome 111+, Edge 111+, Firefox 111+, Safari 16.4+

Removed Features

  • AMP support: All AMP APIs are removed
  • next lint: Use Biome or ESLint directly
  • Sync dynamic APIs: cookies(), headers(), params, and searchParams must be awaited

Behavior Changes

  • Turbopack is the default bundler
  • Image optimization cache TTL increased from 60s to 4 hours
  • All parallel route slots require explicit default.js files

Run the codemod to handle most migrations automatically. See the official upgrade guide for detailed instructions:

npx @next/codemod@canary upgrade latest

Migration Checklist

Update dependencies

Update the dependencies to the latest version:

npm install next@latest react@latest react-dom@latest

Rename middleware to proxy

Rename the middleware file to proxy.ts:

mv middleware.ts proxy.ts

Update the export from middleware to proxy:

export function proxy(request: NextRequest) {

Update async dynamic APIs

const cookieStore = await cookies();
const headerStore = await headers();
const { id } = await params;

Check third-party compatibility

You should check if the third-party libraries you are using are compatible with Next.js 16:

  • OpenNext doesn't support proxy.ts yet
  • Some libraries may not work with React Compiler

Update revalidateTag calls

Add the required profile argument: revalidateTag('tag', 'max')

Test caching behavior

Cache Components work differently than PPR. Verify your caching strategy with the new "use cache" directive.

Profile performance

Run benchmarks before and after. Check for navigation regressions on dynamic routes.

Conclusion

Next.js 16 delivers the performance improvements the community has been waiting for. Turbopack is stable and fast enough for production use, Cache Components provide explicit caching control, and the new caching APIs give you precise invalidation options.

The migration from Next.js 15 is straightforward. Most changes are handled by codemods, and the breaking changes are well-documented.

Our Next.js SaaS Starter Kit ships with Next.js 16 by default. If you're building a new SaaS, you get these performance improvements out of the box.

Frequently Asked Questions

Is Turbopack stable enough for production?
Yes. Turbopack graduated from experimental to stable in Next.js 16 and is now the default bundler. Over 50% of Next.js 15.3+ projects were already using it before the stable release. We've been running it in production across multiple MakerKit customers without issues.
What happened to PPR (Partial Pre-Rendering)?
PPR evolved into Cache Components. Instead of a global ppr flag, you now use the 'use cache' directive to mark specific components or functions for caching.
Do I need to migrate from middleware.ts immediately?
No. middleware.ts still works in Next.js 16 but is deprecated. You should plan to migrate to proxy.ts before it's removed in a future version. The migration is simple: rename the file and change the export name.
Should I enable the React Compiler?
It depends. Enable it if your app has complex client components that re-render frequently. For server-heavy apps like most MakerKit projects, the benefits are minimal. Always profile before and after to verify improvements.
What's the difference between updateTag and revalidateTag?
updateTag blocks until fresh data arrives, giving users immediate feedback. revalidateTag shows stale data while refreshing in the background. Use updateTag for user edits, revalidateTag for content that can tolerate brief staleness.
Is Next.js 16 compatible with OpenNext?
Partially. OpenNext works with Next.js 16 but doesn't support the new proxy.ts convention yet. If you're self-hosting with OpenNext, keep using middleware.ts until support is added.

Resources