Tous les articles
Next.jsReactFrontend

Next.js 16: proxy.ts, React Compiler, Turbopack Default, and the New Cache APIs

//
·9 min de lecture

Next.js 16 deprecates middleware.ts in favour of proxy.ts, makes Turbopack the default dev compiler, stabilises the React Compiler, and overhauls cache invalidation with updateTag() and refresh().

Next.js 16 is the most breaking-change-heavy release since version 13. Node.js 20.9 is now the minimum, middleware.ts is deprecated, the cache model gets three new primitives, and the React Compiler ships stable. If you are on Next.js 15, read the full upgrade guide before updating — the automated codemod covers most of it.

>middleware.ts → proxy.ts

⚠ BREAKINGmiddleware.ts is deprecated. Rename the file to proxy.ts and rename the export from middleware to proxy.

TypeScript
// ✗ Before — middleware.ts (still works, but deprecated)
export function middleware(request: NextRequest) {
  return NextResponse.redirect(new URL('/home', request.url));
}
export const config = { matcher: ['/old-path'] };

// ✓ After — proxy.ts
import { NextRequest, NextResponse } from 'next/server';

export default function proxy(request: NextRequest) {
  return NextResponse.redirect(new URL('/home', request.url));
}
export const config = { matcher: ['/old-path'] };

NOTERun npx @next/codemod@canary upgrade latest to automatically rename the file and export.

>Turbopack stable by default

Turbopack is now the default compiler for next dev. No flag needed. Benchmarks show 2–5× faster cold starts and up to 10× faster Fast Refresh versus webpack. The top-level config key also changed.

TypeScript
// next.config.ts — Next.js 15 (old)
const nextConfig = {
  experimental: { turbopack: true },
};

// next.config.ts — Next.js 16 (new top-level key)
const nextConfig: NextConfig = {
  turbopack: {
    rules: {
      '*.svg': { loaders: ['@svgr/webpack'], as: '*.js' },
    },
  },
};
export default nextConfig;

>React Compiler — stable

The React Compiler automatically memoises components and hooks — no more manual useMemo, useCallback, or React.memo in most cases.

TypeScript
// next.config.ts — enable the compiler
const nextConfig: NextConfig = {
  reactCompiler: true,
};
export default nextConfig;

// Before React Compiler
const ExpensiveList = React.memo(({ items }: { items: Item[] }) => {
  const sorted = useMemo(() => [...items].sort(), [items]);
  const handler = useCallback((id: string) => select(id), []);
  return <ul>{sorted.map(i => <Row key={i.id} item={i} onClick={handler} />)}</ul>;
});

// After React Compiler — same performance, no boilerplate
function ExpensiveList({ items }: { items: Item[] }) {
  const sorted = [...items].sort();
  const handler = (id: string) => select(id);
  return <ul>{sorted.map(i => <Row key={i.id} item={i} onClick={handler} />)}</ul>;
}

>New cache primitives

revalidateTag() — second argument required

⚠ BREAKINGrevalidateTag(tag) with a single argument is deprecated. Pass a cacheLife as the second argument.

TypeScript
import { revalidateTag } from 'next/cache';

// ✗ Deprecated (single argument)
revalidateTag('blog-posts');

// ✓ With cacheLife strategy
revalidateTag('blog-posts', 'max');            // stale-while-revalidate, long TTL
revalidateTag('news-feed',  'hours');          // revalidate every few hours
revalidateTag('cart',       { expire: 300 }); // explicit TTL in seconds

updateTag() — read-your-writes in Server Actions

updateTag() expires a cache entry and ensures the next read is always fresh — designed for Server Actions where you need the UI to reflect a mutation immediately.

TypeScript
'use server';
import { updateTag } from 'next/cache';

export async function updateUserProfile(userId: string, data: Partial<User>) {
  await db.users.update(userId, data);

  // Unlike revalidateTag, updateTag guarantees the next fetch
  // returns fresh data — no stale cache window
  updateTag(`user-${userId}`);
}

// In the component — data is always fresh after the action resolves
async function ProfilePage({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params;
  const user   = await fetchUser(id); // cache-tagged with `user-${id}`
  return <ProfileForm user={user} action={updateUserProfile.bind(null, id)} />;
}

refresh() — refetch dynamic data

TypeScript
'use server';
import { refresh } from 'next/cache';

export async function markAsRead(notificationId: string) {
  await db.notifications.markAsRead(notificationId);
  refresh(); // re-fetches all uncached (dynamic) data in the current route
}

>cacheComponents (replaces dynamicIO)

TypeScript
// next.config.ts
// ✗ Next.js 15 experimental name
const nextConfig = { experimental: { dynamicIO: true } };

// ✓ Next.js 16 stable name
const nextConfig: NextConfig = { cacheComponents: true };

>Other breaking changes

  • Node.js 18 dropped — minimum is Node 20.9.0.
  • AMP support fully removed.
  • next lint command removed — use ESLint or Biome directly.
  • serverRuntimeConfig / publicRuntimeConfig removed — use .env files.
  • images.domains deprecated — use images.remotePatterns.
  • All parallel route slots require explicit default.js files.
  • TypeScript 5.1+ required.

>New in 16.1: Turbopack FS cache + Bundle Analyzer

bash
# Turbopack filesystem cache is stable by default in 16.1
# Restart times up to 14x faster (react.dev: 3.7s → 380ms)

# Bundle Analyzer (experimental)
next experimental-analyze

# Native Node.js debugger (no NODE_OPTIONS workaround)
next dev --inspect

# New upgrade command
next upgrade

>Migration

bash
# Automated codemod — handles most breaking changes
npx @next/codemod@canary upgrade latest

# What the codemod does automatically:
# ✓ Renames middleware.ts → proxy.ts
# ✓ Updates middleware export to proxy
# ✓ Wraps params/searchParams in await
# ✓ Updates experimental.turbopack → turbopack
# ✓ Updates experimental.dynamicIO → cacheComponents
# ✓ Migrates images.domains → images.remotePatterns

# After running the codemod, manually check:
# - revalidateTag() calls (add second argument)
# - parallel routes (add missing default.js files)
# - any AMP pages (remove entirely)