Technology & Development35 min read

Next.js 16: Complete Guide to Cache Components, Turbopack, and Revolutionary Features

Published on 10/22/2025By Prakhar Bhatia
Next.js 16: Complete Guide - Nandann Creative Agency

Next.js 16 Released: On October 21, 2025, ahead of Next.js Conf 2025, Vercel released Next.js 16 with groundbreaking features including Cache Components with Partial Pre-Rendering (PPR), stable Turbopack bundler, and proxy.ts. This comprehensive guide covers every feature, breaking change, and migration path you need to know.

Next.js 16 marks a turning point in how we build web applications. With Cache Components providing explicit, flexible caching, Turbopack delivering 5-10x faster builds, and a complete routing overhaul, this release addresses the biggest pain points developers have faced. Whether you're migrating from Next.js 15 or starting fresh, this guide will show you exactly how to leverage these new capabilities.

In this deep-dive, we'll explore every major feature with practical code examples, performance comparisons, and real-world migration strategies. By the end, you'll understand not just what changed, but why it matters and how to use it effectively.

5-10x

Faster Fast Refresh

2-5x

Faster Production Builds

50%+

Already Using Turbopack

How to Upgrade to Next.js 16

Before diving into the features, let's get you upgraded. Next.js provides both automated and manual upgrade paths:

Automated Upgrade (Recommended)

For the safest migration experience, we recommend using our professional migration tool that provides automatic backups, interactive guidance, and comprehensive analysis:

# Use our professional migration tool
npx nextjs16-migrator

# Or use the basic official codemod
npx @next/codemod@canary upgrade latest

Why choose our tool? Unlike the basic @next/codemod, our tool provides automatic backups, dry-run previews, interactive guidance, and comprehensive compatibility analysis. It's designed for production environments where safety matters.

The codemod will automatically:

  • Update your package.json dependencies
  • Rename middleware.ts to proxy.ts
  • Convert synchronous params and searchParams to async
  • Update async API calls (cookies(), headers(), draftMode())
  • Flag deprecated features for manual review

Manual Upgrade

# Update all Next.js and React packages
npm install next@latest react@latest react-dom@latest

# Or start a fresh project
npx create-next-app@latest

Important: The codemod can't handle every edge case. Check the official upgrade guide for cases requiring manual intervention.

1. Cache Components: Explicit, Flexible Caching

Cache Components represent a fundamental shift in how Next.js handles caching. Unlike the implicit caching in earlier App Router versions, Next.js 16 makes caching entirely opt-in and explicit.

Why Cache Components Matter

In Next.js 15 and earlier App Router versions, determining what would be cached required understanding complex rules about dynamic functions, route segments, and rendering strategies. Cache Components eliminate this confusion:

Aspect Next.js 15 (App Router) Next.js 16 (Cache Components)
Caching Model Implicit - tries to cache by default Explicit - opt-in with "use cache"
Dynamic Code Entire route becomes dynamic Executed at request time by default
Static/Dynamic Choice Route-level decision Component/function-level granularity
PPR Integration Experimental flag Completed with Cache Components
Cache Keys Manual management Compiler-generated automatically

Enabling Cache Components

Enable Cache Components in your Next.js configuration:

// next.config.ts
const nextConfig = {
  cacheComponents: true,
};

export default nextConfig;

Note: The previous experimental.ppr flag has been removed in favor of Cache Components configuration.

Using "use cache" Directive

The "use cache" directive can be applied at three levels:

1. Page-Level Caching

// app/blog/page.tsx
"use cache";

export default async function BlogPage() {
  const posts = await fetchPosts();
  
  return (
    <div>
      {posts.map(post => (
        <Article key={post.id} {...post} />
      ))}
    </div>
  );
}

This caches the entire page output. The compiler automatically generates cache keys based on the route and any dynamic segments.

2. Component-Level Caching

// components/UserProfile.tsx
"use cache";

async function UserProfile({ userId }: { userId: string }) {
  const user = await fetchUser(userId);
  
  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.bio}</p>
    </div>
  );
}

export default UserProfile;

Cache just this component's output. Multiple components on the same page can have different caching strategies.

3. Function-Level Caching

// lib/data.ts
"use cache";

export async function getProductRecommendations(userId: string) {
  const userPreferences = await fetchPreferences(userId);
  const recommendations = await fetchRecommendations(userPreferences);
  return recommendations;
}

Cache function results. Perfect for expensive computations or API calls that don't change frequently.

Cache Components + Partial Pre-Rendering (PPR)

Cache Components complete the vision for Partial Pre-Rendering (PPR), first introduced in 2023. PPR lets you mix static and dynamic content on the same page without forcing an all-or-nothing choice.

Before PPR: A single dynamic element (like a user profile) forced your entire product page to render dynamically, losing the performance benefits of static generation.

With PPR + Cache Components:

// app/product/[id]/page.tsx
import { Suspense } from 'react';

// Static product information (cached)
"use cache";
async function ProductInfo({ id }: { id: string }) {
  const product = await fetchProduct(id);
  return (
    <div>
      <h1>{{product.name}</h1>
      <p>{{product.description}</p>
      <p>{'$'}{{product.price}</p>
    </div>
  );
}

// Dynamic user-specific content (not cached)
async function UserRecommendations({ userId }: { userId: string }) {
  const recommendations = await fetchPersonalizedRecs(userId);
  return <RecommendationGrid items={'{'}recommendations{'}'} />;
}

export default function ProductPage({ params }: { params: { id: string } }) {
  return (
    <div>
      <ProductInfo id={'{'}params.id{'}'} />
      
      <Suspense fallback={'{'}<LoadingSkeleton />{'}'}>
        <UserRecommendations userId={'{'}getCurrentUser().id{'}'} />
      </Suspense>
    </div>
  );
}

Result: The product information loads instantly from the cache (static), while personalized recommendations stream in (dynamic). Users get fast initial load times with personalized content.

Performance Impact

Cache Components with PPR give you the best of both worlds: static page shell loads instantly (sub-100ms), while dynamic content streams in without blocking the initial render. This typically reduces Time to First Byte (TTFB) by 60-80% compared to fully dynamic pages.

Migrating to Next.js 16 or need help with performance optimization?
We specialize in Next.js scalability solutions and can help you leverage these new features for maximum performance.

Talk to Our Next.js Experts

2. Turbopack: Now Stable and Default

Turbopack has reached stability and is now the default bundler for all Next.js projects. Since its beta release, adoption has grown rapidly: over 50% of development sessions and 20% of production builds on Next.js 15.3+ are already using Turbopack.

Performance Improvements

The numbers speak for themselves:

Fast Refresh Speed

Webpack 2.5s
Turbopack 0.25s

10x Faster

Production Build Time

Webpack 180s
Turbopack 45s

4x Faster

These improvements compound over time. If you're making 50 code changes per day, Turbopack saves you roughly 2 hours of waiting for rebuilds.

Opting Out to Webpack

While Turbopack is now the default, you can still use webpack if needed:

# Development with webpack
next dev --webpack

# Production build with webpack
next build --webpack

This is useful if you have custom webpack configurations that aren't yet compatible with Turbopack.

Turbopack Filesystem Caching (Beta)

For large projects, Turbopack now supports filesystem caching in development, storing compiler artifacts between runs:

// next.config.ts
const nextConfig = {
  experimental: {
    turbopackFileSystemCacheForDev: true,
  },
};

export default nextConfig;

This is particularly impactful for large monorepos. Vercel's internal apps have seen startup times improve from minutes to seconds with filesystem caching enabled.

Project Size Without FS Cache With FS Cache Improvement
Small (<100 files) 2s 1.5s 25% faster
Medium (100-1000 files) 15s 5s 67% faster
Large (1000+ files) 120s 12s 90% faster

3. proxy.ts Replaces middleware.ts

Next.js 16 introduces proxy.ts as the new way to intercept requests, replacing middleware.ts. The change clarifies the network boundary and ensures consistent runtime behavior.

Why the Change?

The name "middleware" was ambiguous - it could mean server middleware, edge middleware, or application-level middleware. proxy.ts makes it clear: this code runs at the network boundary, before your application logic.

Additionally, proxy.ts runs on the Node.js runtime (not Edge), providing access to the full Node.js API and better debugging capabilities.

Migration Path

The migration is straightforward:

Before

middleware.ts

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  // Redirect /old-path to /new-path
  if (request.nextUrl.pathname === '/old-path') {
    return NextResponse.redirect(
      new URL('/new-path', request.url)
    );
  }
  
  return NextResponse.next();
}

export const config = {
  matcher: '/about/:path*',
};
After

proxy.ts

// proxy.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export default function proxy(request: NextRequest) {
  // Same logic - just renamed function
  if (request.nextUrl.pathname === '/old-path') {
    return NextResponse.redirect(
      new URL('/new-path', request.url)
    );
  }
  
  return NextResponse.next();
}

export const config = {
  matcher: '/about/:path*',
};

What changed:

  • File renamed: middleware.tsproxy.ts
  • Function renamed: export function middlewareexport default function proxy
  • Logic stays exactly the same
  • Runtime: Now runs on Node.js instead of Edge

Deprecation Notice

middleware.ts still works in Next.js 16 for Edge runtime use cases, but it's deprecated and will be removed in a future version. Migrate to proxy.ts to avoid breaking changes.

4. Next.js Devtools MCP Integration

Next.js 16 introduces Devtools MCP (Model Context Protocol), enabling AI-assisted debugging with full context about your application.

What is Model Context Protocol?

MCP is a standard protocol that allows AI agents to access structured information about your development environment. For Next.js, this means AI assistants can understand:

  • Your routing structure and active routes
  • Caching behavior and configuration
  • Server and browser logs in one unified view
  • Error stack traces with full context
  • Rendering strategies (static, dynamic, ISR)

Example: AI-Assisted Debugging

Before MCP, debugging involved switching between browser DevTools, terminal logs, and documentation. With MCP:

// Your code triggers an error
export default async function Page() {
  const data = await fetch('/api/users');
  const users = data.json(); // ❌ Missing await
  return <UserList users={users} />;
}

Instead of manually copying error messages, you can ask your AI assistant:

"Why is my Page component throwing a TypeError?"

The AI agent, through MCP, has access to:

  • The exact error: TypeError: data.json is not a function
  • The component that failed: /app/users/page.tsx
  • The request URL that triggered the error
  • Whether the page is static or dynamic
  • Related server logs showing the fetch succeeded

The AI can then explain: "You're calling data.json() without await. The json() method returns a Promise. Change line 3 to: const users = await data.json();"

Benefits for Development Workflow

Without MCP

  1. 1. See error in browser
  2. 2. Switch to terminal for server logs
  3. 3. Copy error message to search
  4. 4. Read documentation
  5. 5. Try to understand context
  6. 6. Make educated guess at fix

Time: 10-15 minutes

With MCP

  1. 1. Ask AI: "What's wrong with this page?"
  2. 2. AI analyzes full context automatically
  3. 3. Get specific fix with explanation
  4. 4. Apply the fix

Time: 1-2 minutes

MCP doesn't replace your debugging skills - it augments them by handling the tedious parts of context gathering and log searching.

5. Enhanced Routing and Navigation

Next.js 16 includes a complete overhaul of the routing system, making navigation faster and more efficient through layout deduplication and incremental prefetching.

Layout Deduplication

One of the biggest improvements: when prefetching multiple URLs that share a layout, the layout is downloaded once, not separately for each link.

Scenario: You have a product listing page with 50 product links, all sharing the same layout (header, footer, sidebar).

Next.js 15 Behavior

Each link prefetches:

  • • Layout (35KB) × 50 = 1.75MB
  • • Product page (10KB) × 50 = 500KB
  • Total: 2.25MB

Next.js 16 Behavior

Layout deduplicated:

  • • Layout (35KB) × 1 = 35KB
  • • Product page (10KB) × 50 = 500KB
  • Total: 535KB

76% Data Transfer Reduction

Incremental Prefetching

Next.js 16 only prefetches what's not already in cache, rather than entire pages. The prefetch cache now:

  • Cancels requests when links leave the viewport (saves bandwidth)
  • Prioritizes links on hover or when re-entering the viewport
  • Re-prefetches links when their data is invalidated (after mutations)
  • Works with Cache Components for even smarter prefetching

Code Example: Smart Prefetching

// app/products/page.tsx
import Link from 'next/link';

export default function ProductsPage({ products }) {
  return (
    <div>
      {/* Next.js 16 intelligently prefetches these links */}
      {products.map(product => (
        <Link 
          key={product.id} 
          href={`/products/${product.id}`}
          prefetch={true} // Default behavior
        >
          <ProductCard {...product} />
        </Link>
      ))}
    </div>
  );
}

Behind the scenes:

  1. First 10 visible links are prefetched immediately
  2. Layout is fetched once and shared
  3. When you scroll, new links entering viewport are prefetched
  4. When you scroll back up, links leaving viewport have their prefetch requests cancelled
  5. On hover, that specific link is prioritized

Trade-offs to Consider

While you'll see more individual prefetch requests in DevTools, the total data transfer is significantly lower. This is the right trade-off for nearly all applications:

Metric Next.js 15 Next.js 16
Number of requests 50 51 (1 layout + 50 pages)
Total data transfer 2.25MB 535KB
Duplicate data 1.75MB (49 duplicate layouts) 0KB
Navigation speed Instant Instant (with less data)

If the increased request count causes issues (unlikely), you can adjust prefetch behavior:

<Link href="/product" prefetch={false}>
  {/* Only prefetch on hover */}
</Link>

6. Improved Caching APIs

Next.js 16 introduces refined caching APIs that give you explicit control over cache behavior while maintaining performance.

revalidateTag() - Now Requires cacheLife Profile

The revalidateTag() API has been updated to require a cacheLife profile as the second argument, enabling stale-while-revalidate (SWR) behavior:

Deprecated

Old API

// Next.js 15
revalidateTag('blog-posts');

// No control over revalidation behavior
// No stale-while-revalidate support
New

New API

// Next.js 16
import { revalidateTag } from 'next/cache';

// Use built-in cacheLife profile
revalidateTag('blog-posts', 'max');

// Or use other profiles
revalidateTag('news-feed', 'hours');
revalidateTag('analytics', 'days');

// Or inline custom revalidation
revalidateTag('products', { revalidate: 3600 });

Built-in cacheLife Profiles

Profile Revalidate Time Best For
max As long as possible Static content that rarely changes
days 24 hours Content that updates daily
hours 1 hour Frequently updated content
minutes 5 minutes Near real-time content

Recommendation: Use 'max' for most cases. It enables background revalidation for long-lived content - users get cached data immediately while Next.js revalidates in the background.

updateTag() - New API for Server Actions

updateTag() is a Server Actions-only API that provides read-your-writes semantics:

'use server';

import { updateTag } from 'next/cache';

export async function updateUserProfile(userId: string, profile: Profile) {
  // Update database
  await db.users.update(userId, profile);
  
  // Expire cache AND immediately read fresh data
  updateTag(`user-${userId}`);
  
  // User sees their changes instantly
}

This is perfect for interactive features where users expect to see their changes immediately:

  • Form submissions
  • User settings updates
  • Profile edits
  • Shopping cart modifications

refresh() - New API for Uncached Data

refresh() is for refreshing uncached data only - it doesn't touch the cache at all:

'use server';

import { refresh } from 'next/cache';

export async function markNotificationAsRead(notificationId: string) {
  // Update notification in database
  await db.notifications.markAsRead(notificationId);
  
  // Refresh the notification count in the header
  // (which is fetched dynamically, not cached)
  refresh();
}

Use refresh() when you need to update dynamic data displayed elsewhere on the page:

  • Notification counts
  • Live metrics and stats
  • Status indicators
  • Real-time dashboards

Your cached page shells and static content remain fast, while only dynamic data refreshes.

When to Use Each API

revalidateTag()

For cached content with SWR

  • ✓ Blog posts
  • ✓ Product listings
  • ✓ Static pages
  • ✓ Eventual consistency OK

updateTag()

For immediate updates (Server Actions)

  • ✓ User profiles
  • ✓ Form submissions
  • ✓ Settings changes
  • ✓ Must see changes now

refresh()

For uncached dynamic data

  • ✓ Live counters
  • ✓ Notifications
  • ✓ Real-time metrics
  • ✓ Dynamic indicators

7. React 19.2 & Canary Features

Next.js 16 uses the latest React Canary release, which includes React 19.2 features and other incrementally stabilized capabilities.

View Transitions

Animate elements that update inside a Transition or navigation:

import { useTransition, startTransition } from 'react';

function ProductGallery() {
  const [isPending, startTransition] = useTransition();
  const [selectedImage, setSelectedImage] = useState(0);
  
  return (
    <div>
      <img 
        src={images[selectedImage]} 
        style={{
          viewTransitionName: 'product-image',
          opacity: isPending ? 0.8 : 1,
        }}
      />
      
      <div>
        {images.map((img, i) => (
          <button
            key={i}
            onClick={() => {
              startTransition(() => {
                setSelectedImage(i);
              });
            }}
          >
            <img src={img} />
          </button>
        ))}
      </div>
    </div>
  );
}

The image smoothly transitions between states instead of instantly swapping.

useEffectEvent()

Extract non-reactive logic from Effects into reusable Effect Event functions:

import { useEffect, useEffectEvent } from 'react';

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');
  
  // Extract logging logic that shouldn't trigger re-renders
  const onMessage = useEffectEvent((msg) => {
    console.log('Message in room', roomId, ':', msg);
    analytics.track('message_sent', { roomId, length: msg.length });
  });
  
  useEffect(() => {
    const connection = createConnection(roomId);
    connection.on('message', onMessage);
    return () => connection.disconnect();
  }, [roomId]); // onMessage is not a dependency
  
  // ...
}

This solves the common problem of having event handlers in Effects that shouldn't trigger re-subscriptions.

<Activity /> Component

Render "background activity" by hiding UI with display: none while maintaining state and cleaning up Effects:

import { Activity } from 'react';

function Dashboard() {
  const [activeTab, setActiveTab] = useState('home');
  
  return (
    <div>
      <Tabs value={activeTab} onChange={setActiveTab} />
      
      {/* Keep all tabs mounted but hidden when inactive */}
      <Activity mode={activeTab === 'home' ? 'visible' : 'hidden'}>
        <HomeTab />
      </Activity>
      
      <Activity mode={activeTab === 'analytics' ? 'visible' : 'hidden'}>
        <AnalyticsTab />
      </Activity>
      
      <Activity mode={activeTab === 'settings' ? 'visible' : 'hidden'}>
        <SettingsTab />
      </Activity>
    </div>
  );
}

This is perfect for tab interfaces where you want instant switching without losing state, but don't want hidden tabs consuming resources.

8. Breaking Changes & Migration

Next.js 16 includes several breaking changes. Here's what you need to know and how to migrate:

Version Requirements

Dependency Minimum Version Notes
Node.js 20.9.0+ Node 18 no longer supported
TypeScript 5.1.0+ Required for async params types
Chrome 111+ For View Transitions support
Safari 16.4+ Modern JavaScript features

Async params and searchParams

One of the biggest changes: params and searchParams are now async and must be awaited:

Before (Sync)

// Next.js 15
export default function Page({ 
  params,
  searchParams 
}: {
  params: { id: string };
  searchParams: { sort: string };
}) {
  // Direct access
  const id = params.id;
  const sort = searchParams.sort;
  
  return <div>Product {id}</div>;
}

After (Async)

// Next.js 16
export default async function Page({ 
  params,
  searchParams 
}: {
  params: Promise<{ id: string }>;
  searchParams: Promise<{ sort: string }>;
}) {
  // Must await
  const { id } = await params;
  const { sort } = await searchParams;
  
  return <div>Product {id}</div>;
}

Async Cookie, Headers, and DraftMode APIs

Similarly, cookies(), headers(), and draftMode() must now be awaited:

// Before
import { cookies } from 'next/headers';

export function getAuthToken() {
  const cookieStore = cookies();
  return cookieStore.get('token');
}

// After
import { cookies } from 'next/headers';

export async function getAuthToken() {
  const cookieStore = await cookies();
  return cookieStore.get('token');
}

Removed Features

Removed Feature Replacement
AMP support All AMP APIs removed. Use responsive design instead.
next lint command Use ESLint or Biome directly. Codemod available: npx @next/codemod@canary next-lint-to-eslint-cli
serverRuntimeConfig Use environment variables (.env files)
experimental.ppr flag Use cacheComponents configuration
Local image URLs with query strings Requires images.localPatterns config for security

Behavior Changes

These features have new default behaviors in Next.js 16:

  • Default bundler: Turbopack (was webpack). Opt out with next build --webpack
  • images.minimumCacheTTL: Now 4 hours (was 60s)
  • images.imageSizes: Removed 16px from defaults (used by only 4.2% of projects)
  • images.qualities: Now [75] (was [1..100]). Quality prop coerced to closest value
  • images.dangerouslyAllowLocalIP: Blocks local IP optimization by default (security)
  • Parallel routes: All slots now require explicit default.js files

9. Build Adapters API (Alpha)

The new Build Adapters API allows you to hook into the build process to modify configuration or process build output.

Use Cases

  • Custom deployment platforms
  • Build output transformation
  • Custom serverless function generation
  • Integration with proprietary infrastructure

Creating a Build Adapter

// my-adapter.js
module.exports = function myAdapter() {
  return {
    name: 'my-custom-adapter',
    
    // Modify Next.js config during build
    async modifyConfig(config) {
      return {
        ...config,
        // Your modifications
      };
    },
    
    // Process build output
    async onBuildComplete(result) {
      console.log('Build completed:', result);
      // Transform or move files
    },
  };
};

Using the Adapter

// next.config.js
const nextConfig = {
  experimental: {
    adapterPath: require.resolve('./my-adapter.js'),
  },
};

module.exports = nextConfig;

Build Adapters are in alpha. Share feedback in the RFC discussion to help shape the final API.

10. React Compiler Support (Stable)

React Compiler support is now stable in Next.js 16, following the React Compiler 1.0 release.

What is React Compiler?

React Compiler automatically memoizes your components, reducing unnecessary re-renders without manual useMemo, useCallback, or React.memo.

Without React Compiler

function UserProfile({ user }) {
  // Need manual memoization
  const fullName = useMemo(
    () => `${user.first} ${user.last}`,
    [user.first, user.last]
  );
  
  const handleClick = useCallback(() => {
    saveUser(user.id);
  }, [user.id]);
  
  return (
    <div onClick={handleClick}>
      {fullName}
    </div>
  );
}

With React Compiler

function UserProfile({ user }) {
  // Automatic memoization
  const fullName = `${user.first} ${user.last}`;
  
  const handleClick = () => {
    saveUser(user.id);
  };
  
  return (
    <div onClick={handleClick}>
      {fullName}
    </div>
  );
}

Enabling React Compiler

// next.config.ts
const nextConfig = {
  reactCompiler: true,
};

export default nextConfig;

Then install the plugin:

npm install babel-plugin-react-compiler@latest

Performance Trade-offs

React Compiler is not enabled by default because it relies on Babel, which increases compile times:

Scenario Without Compiler With Compiler
Dev server startup 3s 5-7s
Fast Refresh 0.3s 0.5-0.8s
Production build 45s 60-90s

When to enable: If your app has performance issues from excessive re-renders, React Compiler can help significantly. The build time cost is worth it for runtime performance gains. If your app already performs well, you may not need it.

Key Takeaways

Must-Know Changes

  • ✓ Upgrade to Node.js 20.9+
  • ✓ Make params/searchParams async
  • ✓ Rename middleware.ts → proxy.ts
  • ✓ Update revalidateTag() calls with cacheLife
  • ✓ Add default.js to parallel route slots

Biggest Wins

  • ✓ 5-10x faster dev experience with Turbopack
  • ✓ Explicit caching with Cache Components
  • ✓ 76% less prefetch data transfer
  • ✓ AI-assisted debugging with MCP
  • ✓ Better cache control APIs

Next.js 16 is a major leap forward in developer experience and application performance. The combination of explicit caching, Turbopack's speed improvements, and smarter routing creates a foundation for building faster, more maintainable web applications.

Ready to Upgrade to Next.js 16?

For a safer, more comprehensive migration experience, we recommend using our professional migration tool:

npx nextjs16-migrator

This tool provides automatic backups, interactive guidance, and comprehensive analysis - much safer than the basic @next/codemod.

Why Choose Our Migration Tool?

Safety Features:

  • • Automatic git commits & backups
  • • One-command rollback
  • • Dry-run preview mode

Professional Features:

  • • Interactive CLI with progress indicators
  • • Comprehensive compatibility analysis
  • • Detailed migration reports

Learn more about our migration tool →

Need Expert Help with Your Migration?

Our team specializes in WordPress to Next.js migrations and complex Next.js upgrades. We'll handle the migration, optimize for Core Web Vitals, and ensure zero downtime.

Get Your Free Migration Consultation

Same-day response • No obligation • Expert guidance

Next.js Conf 2025 is happening on October 22nd with more deep-dives into Cache Components, Turbopack internals, and advanced patterns. Expect additional blog posts and documentation updates in the coming weeks.

FAQs

Should I upgrade to Next.js 16 immediately?

If you're starting a new project, yes - Next.js 16 is stable and production-ready. For existing applications, review the breaking changes first. The biggest considerations are Node.js 20.9+ requirement and async params/searchParams. Use the automated codemod to handle most migrations, then test thoroughly before deploying to production.

What's the difference between Cache Components and the old App Router caching?

The old App Router tried to cache everything by default (implicit caching), which was confusing and unpredictable. Cache Components make caching entirely opt-in using the 'use cache' directive. This gives you explicit control: by default, all dynamic code runs at request time. You choose what to cache at the page, component, or function level. It's clearer, more flexible, and easier to reason about.

Is Turbopack stable enough for production?

Yes. Turbopack is now stable and is the default bundler in Next.js 16. It's been extensively tested and is already used in 20% of production builds on Next.js 15.3+. Major companies including Vercel's internal apps are running Turbopack in production. If you encounter issues with custom webpack configurations, you can still opt back to webpack with next build --webpack.

Do I need to rename middleware.ts to proxy.ts?

It's strongly recommended but not immediately required. middleware.ts still works in Next.js 16 but is deprecated and will be removed in a future version. The migration is simple: rename the file to proxy.ts and rename the exported function from middleware to proxy. Everything else stays the same. The automated codemod handles this for you.

Why did Next.js make params and searchParams async?

This change enables better streaming and concurrent rendering optimizations. By making these async, Next.js can start rendering your page before all params are resolved, improving Time to First Byte (TTFB). It also aligns with the async nature of modern React Server Components. The migration is straightforward: add async to your page function and await params/searchParams.

How do I know when to use revalidateTag() vs updateTag() vs refresh()?

Use revalidateTag() for cached content where eventual consistency is acceptable (blog posts, product listings). Use updateTag() in Server Actions when users need to see their changes immediately (profile updates, form submissions). Use refresh() for uncached dynamic data that needs updating (notification counts, live metrics). revalidateTag enables stale-while-revalidate, updateTag provides read-your-writes, and refresh only touches uncached data.

Will Next.js 16 work with React 18?

Next.js 16 requires React 19.2 or later. The App Router relies on React Server Components and other features only available in React 19+. If you're still on React 18, you'll need to upgrade React when you upgrade Next.js. The good news is that React 19 is stable and the upgrade path is well-documented.

What are the performance benefits I can expect from upgrading?

With Turbopack, expect 5-10x faster Fast Refresh during development and 2-5x faster production builds. Layout deduplication can reduce prefetch data transfer by 60-80% on pages with many links. Cache Components with PPR can improve initial page load times by 60-80% compared to fully dynamic pages. Exact improvements depend on your application structure and caching strategy.

Should I enable the React Compiler?

Enable it if your app has performance issues from excessive re-renders or if you want to reduce manual memoization. Don't enable it if your app already performs well and you want faster build times. React Compiler adds significant compile time overhead because it uses Babel. Test both with and without to see if the runtime performance gains justify the build time cost for your specific application.

What's Partial Pre-Rendering (PPR) and how does it work with Cache Components?

PPR lets you mix static (cached) and dynamic (uncached) content on the same page. Before PPR, one dynamic element forced the entire page to be dynamic. With PPR + Cache Components, you mark what should be cached with 'use cache' and wrap dynamic parts in Suspense boundaries. The static shell loads instantly while dynamic content streams in. This gives you the best of both worlds: fast initial loads with personalized content.

How do I handle the new images.qualities behavior?

Next.js 16 changed images.qualities from [1..100] to [75] by default, meaning the quality prop is coerced to the closest value in the array. If you need different quality levels, explicitly configure images.qualities in next.config.ts: { images: { qualities: [50, 75, 90] } }. This reduces the number of image variations Next.js generates, improving build performance.

What happened to AMP support?

AMP support has been completely removed in Next.js 16. Google no longer prioritizes AMP in search rankings, and modern responsive design with good Core Web Vitals achieves the same goals. If you were using AMP, focus on optimizing your regular pages for performance using Next.js's built-in optimizations, Cache Components, and Turbopack. Most sites no longer need AMP.