Next.js 16 New Features and Breaking Changes — proxy.ts, Cache, Turbopack

Table of Contents

// proxy.ts
export default function proxy(request: NextRequest) {
  return NextResponse.redirect(new URL('/home', request.url));
}

What is this code? It’s the most immediately noticeable part of the Next.js 16 new features and breaking changes. The existing middleware.ts has been renamed to proxy.ts. It’s not just a file rename — the runtime has changed and the exported function name is different too. Several breaking changes are included that prevent Next.js 15 code from working as-is in version 16, so checking before upgrading is essential.

This article covers the three major breaking changes and two new APIs in Next.js 16, organized around code examples.

Next.js 16 proxy.ts: What Changed from middleware

proxy.ts middleware change

The biggest change in Next.js 16 is the rename of middleware.ts to proxy.ts. This isn’t a simple rename — it’s effectively a runtime swap.

Here are the specific changes:

  • File name: middleware.tsproxy.ts
  • Exported function: middlewareproxy
  • Runtime: Edge Runtime → Node.js Runtime
  • Config key: skipMiddlewareUrlNormalizeskipProxyUrlNormalize

The existing middleware.ts is now deprecated as an Edge Runtime-only artifact. The background for this change is documented in the Next.js 16 release notes.

// proxy.ts
export default function proxy(request: NextRequest) {
  return NextResponse.redirect(new URL('/home', request.url));
}

The key thing to notice here is that the function name is proxy. This is incompatible with the previous export function middleware() pattern. If next.config.ts uses the URL normalization skip option, the key name needs to change as well.

middleware.ts Compatibility Warning
In Next.js 16, middleware.ts is deprecated and Edge Runtime-only. To use Node.js runtime features (direct DB access, fs module, etc.), migration to proxy.ts is required.

The migration itself comes down to two steps — renaming the file and changing the function name. However, if any Edge Runtime-dependent libraries were in use, Node.js runtime compatibility needs to be verified as well, which adds complexity. The official documentation notes that the Edge Runtime alternative guide for proxy.ts is still incomplete and will be supplemented in a future minor release.

proxy.ts Migration Steps

Follow this sequence for the actual migration:

  1. Rename middleware.ts to proxy.ts
  2. Change export function middleware to export default function proxy
  3. Update skipMiddlewareUrlNormalize to skipProxyUrlNormalize in next.config.ts
  4. Check for Edge-only API usage and test whether everything works under the Node.js runtime

Next.js 16 Async-Only Dynamic API Changes

The second major breaking change in Next.js 16 is the complete removal of synchronous access to Dynamic APIs. cookies(), headers(), draftMode(), params, and searchParams can now only be accessed with await.

In Next.js 15, synchronous access produced a deprecation warning. In 16, it throws an error. This has the potential to break a significant amount of existing code.

// page.tsx
export default async function Page(props: PageProps<'/blog/[slug]'>) {
  const { slug } = await props.params
  const query = await props.searchParams
  return <h1>Blog Post: {slug}</h1>
}

In the code above, both props.params and props.searchParams require await. The id parameter in sitemap and params in opengraph-image have also been changed to Promise, which is worth noting.

APINext.js 15Next.js 16
cookies()Sync access allowed (warning)await required
headers()Sync access allowed (warning)await required
paramsSync access allowed (warning)await required
searchParamsSync access allowed (warning)await required
draftMode()Sync access allowed (warning)await required
sitemap idSyncPromise
opengraph-image paramsSyncPromise
Quick Migration Tip
To find all places in a project where `cookies()`, `headers()`, `params`, or `searchParams` are used without `await`, enable TypeScript strict mode and run a build. Type errors will flag every location.

The reason this change has such a wide blast radius is that it applies not only to layout and page components but also to functions like generateMetadata and generateStaticParams. The Next.js 16 upgrade guide covers the full scope in detail — reading through the entire document before migrating is recommended.

Patterns to Watch During Async Migration

A common gotcha: the pattern of destructuring params directly in the function signature no longer works.

// ❌ Does not work with Next.js 16
export default function Page({ params: { slug } }) {
  return <h1>{slug}</h1>
}
// ✅ Next.js 16 method
export default async function Page(props: PageProps<'/blog/[slug]'>) {
  const { slug } = await props.params
  return <h1>{slug}</h1>
}

Since the component function now needs async, this pattern can’t be used directly in client components. The solution is to await the values in a server component and pass them down as props to client components.

Turbopack as Default Bundler and Next.js 16 Build Performance

Turbopack bundler performance

Turbopack has stabilized and become the default bundler in Next.js 16. Both next dev and next build use Turbopack by default, delivering up to 10x faster Fast Refresh and 2–5x faster production builds according to the official announcement.

This also qualifies as a breaking change: the experimental.turbopack config has moved to a top-level turbopack key.

// next.config.ts (Next.js 16)
const nextConfig: NextConfig = {
  turbopack: {
    // options
  },
};

export default nextConfig;

If options were previously nested under experimental.turbopack, the config path needs to be updated. To keep using webpack, opt out with the --webpack flag.

Itemwebpack (opt-out)Turbopack (default)
Fast RefreshBaselineUp to 10x faster
Production buildBaseline2–5x faster
Config locationNo flag neededturbopack: {} (top-level)
Activation--webpack flagDefault
Verify Turbopack Config Migration
If `experimental.turbopack` was in use in `next.config.ts`, it must be moved to the top-level `turbopack` key. Leaving the config in the old path causes it to be silently ignored, meaning build options won’t take effect.

Checkpoints for Switching from webpack to Turbopack

Turbopack doesn’t support every webpack plugin. Projects with extensive custom webpack configurations should check the following:

  • Custom loaders added via the webpack callback → verify Turbopack-compatible loaders exist
  • webpack-only plugins like next-bundle-analyzer → check for Turbopack alternatives
  • Custom module.rules → map to Turbopack’s rules configuration

For larger projects, a safer strategy is to test Turbopack in the development environment (next dev) first while keeping production builds on --webpack, then migrate incrementally.

Next.js 16 Cache Components: “use cache” Directive

Next.js 16 introduces Cache Components, fundamentally changing the caching model. The "use cache" directive enables explicit caching of pages, components, and functions, with the compiler automatically generating cache keys.

The previously used experimental.ppr and experimental.dynamicIO flags have been removed and replaced by the cacheComponents setting.

const nextConfig = {
  cacheComponents: true,
};

export default nextConfig;

Setting cacheComponents: true enables recognition of the "use cache" directive. The directive is declared in the same position as "use client" or "use server".

Here’s how Cache Components work:

  1. Declare "use cache" at the top of a component or function
  2. The compiler analyzes the unit’s inputs (props, arguments) and auto-generates a cache key
  3. When the same inputs arrive, the cached result is returned
  4. Cache invalidation is handled via updateTag() or revalidateTag()
Experimental Flag Removal Notice
If `experimental.ppr` or `experimental.dynamicIO` are in `next.config`, remove them and replace with `cacheComponents: true`. Leaving the old flags in place may trigger config validation warnings.

That said, in-depth examples of "use cache" in production scenarios are still lacking even in the official documentation. Basic page- and component-level caching is documented, but cache key generation strategies for complex data dependencies and nested cache invalidation patterns are not yet specified.

Cache Components Activation Checklist

Items to verify before applying Cache Components:

  • cacheComponents: true added to next.config.ts
  • experimental.ppr and experimental.dynamicIO removed
  • Components using the "use cache" directive are pure functions (same input → same output)
  • Cache invalidation strategy defined (updateTag vs revalidateTag)

Next.js 16 updateTag vs revalidateTag Cache Invalidation Compared

The cache invalidation API has also changed significantly in Next.js 16. updateTag() is a new Server Actions-only API that provides read-your-writes semantics — its most notable characteristic. It expires the cache and allows reading fresh data within the same request.

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

export async function updateUserProfile(userId: string, profile: Profile) {
  await db.users.update(userId, profile);
  updateTag(`user-${userId}`);
}

revalidateTag() has also changed. A second argument specifying a cacheLife profile (e.g., 'max') is now required. Passing only the tag as before produces a type error.

refresh() has been added separately as a Server Actions-only API that refreshes only non-cached data.

APIPurposeCached dataNon-cached dataReflected in same request
updateTag()Immediate cache update✅ Expires + immediate✅ (read-your-writes)
revalidateTag()Schedule revalidation✅ Reflected on next request
refresh()Non-cache refresh

Whether to use updateTag() or revalidateTag() depends on UX requirements. When a user needs to see updated values immediately after a profile edit, updateTag() is the right choice. When background cache updates are acceptable, revalidateTag() is sufficient.

updateTag Use Case
If users were seeing stale cached data after submitting a form and viewing the results page, updateTag() solves this. Calling it right after a DB update inside a Server Action ensures the new data is reflected in the same response.

Next.js 16 Migration Checklist

Here’s a structured migration sequence covering the three breaking changes and two new APIs in Next.js 16. Handle breaking changes first, then apply new APIs incrementally.

Step 1: Breaking Changes (Required)

[마이그레이션 체크리스트]
├── middleware.ts → proxy.ts
│   ├── 파일명 변경
│   ├── export function middleware → export default function proxy
│   └── skipMiddlewareUrlNormalize → skipProxyUrlNormalize
├── 비동기 API 전환
│   ├── cookies() → await cookies()
│   ├── headers() → await headers()
│   ├── params → await props.params
│   ├── searchParams → await props.searchParams
│   └── draftMode() → await draftMode()
└── Turbopack 설정
    ├── experimental.turbopack → turbopack (최상위)
    └── webpack 의존성 호환 테스트

Step 2: New APIs (Optional)

  • Enable cacheComponents: true and apply the "use cache" directive
  • Add the second cacheLife profile argument to revalidateTag() call sites
  • Introduce updateTag() in Server Actions that require immediate reflection
  • Separate non-cached data refresh logic using the refresh() API

A Korean-language official migration guide is not yet available. Migration work should be based on the English upgrade guide.

Next Steps After Upgrading to Next.js 16

The core of Next.js 16 new features and breaking changes boils down to three breaking changes (proxy.ts migration, mandatory async APIs, Turbopack as default) and two new APIs (Cache Components, updateTag).

After completing the migration, three additional areas deserve attention. First is designing granular cache strategies using Next.js 16 Cache Components. With caching now controllable at the component and function level rather than the page level, deciding where to draw cache boundaries based on data update frequency becomes an important design task.

Optimizing the build pipeline under the Turbopack default bundler environment is also worth reviewing. Converting webpack plugin ecosystem dependencies to native Turbopack approaches can maximize build speed gains. Additionally, React 19.2 integration with Next.js — particularly features like View Transitions — still lacks production-ready examples, so keeping an eye on official documentation updates is advisable.

Once the Next.js 16 async params and searchParams patterns become familiar, the data flow design between server and client components will naturally fall into place. After applying the Next.js 16 proxy.ts middleware changes, the core upgrade work is complete.

Scroll to Top