Next.js 16: Enhancing Security with proxy.ts, Dynamic Caching, and updateTag()
Next.js 16 brings significant updates to caching, routing, and request interception, enhancing authentication and authorization. Learn about proxy.ts, dynamic defaults, and updateTag() for clearer security boundaries.
Next.js 16 introduces a suite of practical updates that refine how the framework manages caching, routing, and request interception. For developers focused on authentication and authorization, these changes provide clearer boundaries and more predictable defaults. While not reinventing core concepts, they offer improved tools for defining where and how security logic should operate.
This article explores the specific features in Next.js 16 and their implications for securing your applications.
Clarifying the Network Boundary with proxy.ts
The most significant change for security configuration is the renaming and clearer definition of the middleware file.
The Change: middleware.ts is now proxy.ts
Next.js 16 renames middleware.ts to proxy.ts. While the core functionality of intercepting requests remains similar, the new name more accurately reflects its role as a lightweight routing layer rather than a location for extensive business logic.
You can now export a named proxy function from a proxy.ts file in your root or /src directory:
// proxy.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function proxy(request: NextRequest) {
// Lightweight authentication check
const token = request.cookies.get('auth_token');
if (!token) {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
}
The Security Impact
This change reinforces a crucial architectural pattern. The documentation emphasizes that proxy.ts is not intended for full session management or complex authorization.
- Keep it light: Use
proxy.tsfor high-level traffic control, such as redirecting users who lack a session cookie. - Move complexity downstream: Detailed authentication (like validating JWT signatures) and granular authorization (like checking specific user permissions) should reside closer to your data in Server Components or Server Actions. This approach leverages the stable Node.js runtime and keeps your edge logic simple.
Cache Components and Dynamic by Default
Next.js 16 introduces a new default caching behavior that aligns well with modern security requirements.
The Change: Opt-in Caching
When you enable cacheComponents: true in your configuration, the framework no longer attempts to cache dynamic data by default. Instead, data fetching operations run at request time unless you explicitly use the use cache directive.
The Authorization Benefit
This shift significantly reduces the risk of accidental data leaks.
- Fresh data: Because components run dynamically by default, you can be confident that authorization checks (e.g., fetching user roles) occur in real-time for every request.
- Explicit caching: You only cache data when you specifically intend to, which necessitates a conscious decision about what content is safe to store.
A Note on use cache
When you do use the use cache directive for personalized data, meticulous care with your cache keys is essential. The directive generates a key based on the arguments passed to the function.
To prevent one user from inadvertently seeing another user's cached data, always pass a unique user identifier as an argument:
'use cache';
// The cache key is derived from the `userId` argument
// This ensures Next.js creates a unique entry for each user
export async function getCachedUserDashboard(userId: string) {
const data = await db.findDashboard(userId);
return data;
}
Server Actions and updateTag()
Server Actions remain the standard method for handling mutations (such as updating a user profile). Next.js 16 adds a specific tool to manage cache invalidation after these updates.
The New API: updateTag()
When a user performs an action that modifies their permissions (for instance, an administrator bans a user, or a user upgrades their subscription plan), it's crucial for that change to reflect immediately.
The updateTag() API allows you to expire a specific cache tag and refresh the data within the same request:
'use server';
import { updateTag } from 'next/cache';
export async function changeUserRole(userId: string, newRole: string) {
// 1. Update the user's role in the database
await db.users.update(userId, { role: newRole });
// 2. Invalidate the cache and refresh the data immediately
updateTag(`user-profile-${userId}`);
}
The Security Benefit
This feature supports "read-your-writes" semantics, which is critical in a security context. It ensures that if you revoke a permission, the application recognizes that change instantly on the very next render, rather than inadvertently serving stale, authorized content from the cache.
Next.js 16 Makes Authentication and Authorization Boundaries Clear
Next.js 16 introduces a set of refinements that make the security model more explicit:
proxy.tsclarifies the role of request interception.- Dynamic defaults reduce the chance of accidental caching issues.
updateTag()ensures that permission changes take effect immediately.
These updates help developers build applications where authentication and authorization boundaries are clear, predictable, and easier to maintain.
We are actively working to incorporate these new Next.js 16 features into the official Auth0 Next.js SDK. To stay informed on our progress and track the release of these updates, you can check out the Auth0 Next.js SDK GitHub project on GitHub.
About the Author

Will Johnson
Developer Advocate
Will Johnson is a Developer Advocate at Auth0. He is passionate about teaching complex topics in an accessible way to empower more developers. He contributes through blog posts, screencasts, and eBooks. He is also an instructor on egghead.io.