Table of Contents
- Next.js 16 proxy.ts: What Changed from middleware
- Next.js 16 Async-Only Dynamic API Changes
- Turbopack as Default Bundler and Next.js 16 Build Performance
- Next.js 16 Cache Components: “use cache” Directive
- Next.js 16 updateTag vs revalidateTag Cache Invalidation Compared
- Next.js 16 Migration Checklist
- Next Steps After Upgrading to Next.js 16
// 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

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.ts→proxy.ts - Exported function:
middleware→proxy - Runtime: Edge Runtime → Node.js Runtime
- Config key:
skipMiddlewareUrlNormalize→skipProxyUrlNormalize
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.
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:
- Rename
middleware.tstoproxy.ts - Change
export function middlewaretoexport default function proxy - Update
skipMiddlewareUrlNormalizetoskipProxyUrlNormalizeinnext.config.ts - 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.
| API | Next.js 15 | Next.js 16 |
|---|---|---|
cookies() | Sync access allowed (warning) | await required |
headers() | Sync access allowed (warning) | await required |
params | Sync access allowed (warning) | await required |
searchParams | Sync access allowed (warning) | await required |
draftMode() | Sync access allowed (warning) | await required |
sitemap id | Sync | Promise |
opengraph-image params | Sync | Promise |
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 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.
| Item | webpack (opt-out) | Turbopack (default) |
|---|---|---|
| Fast Refresh | Baseline | Up to 10x faster |
| Production build | Baseline | 2–5x faster |
| Config location | No flag needed | turbopack: {} (top-level) |
| Activation | --webpack flag | Default |
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
webpackcallback → verify Turbopack-compatible loaders exist - webpack-only plugins like
next-bundle-analyzer→ check for Turbopack alternatives - Custom
module.rules→ map to Turbopack’srulesconfiguration
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:
- Declare
"use cache"at the top of a component or function - The compiler analyzes the unit’s inputs (props, arguments) and auto-generates a cache key
- When the same inputs arrive, the cached result is returned
- Cache invalidation is handled via
updateTag()orrevalidateTag()
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: trueadded tonext.config.tsexperimental.pprandexperimental.dynamicIOremoved- Components using the
"use cache"directive are pure functions (same input → same output) - Cache invalidation strategy defined (
updateTagvsrevalidateTag)
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.
| API | Purpose | Cached data | Non-cached data | Reflected 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.
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: trueand apply the"use cache"directive - Add the second
cacheLifeprofile argument torevalidateTag()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.
Related Posts
- Getting Started with Next.js 16 — 7-Step Complete Guide to proxy.ts Migration and Turbopack Setup – Next.js 16 replaces middleware.ts with proxy.ts and makes Turbopack the default bundler. From project creation to …
- What Is Search Engine Optimization (SEO)? – [ez-toc] What Is Search Engine Optimization (SEO)? Search engine optimization (SEO) is an essential part of our daily internet use…