Getting Started with Next.js 16 — proxy.ts Migration and Turbopack Setup Guide

Table of Contents

Before diving into Next.js 16, there are two things to know first. middleware.ts has been replaced by proxy.ts, and Turbopack is now the default bundler. Attempting to deploy existing Next.js 15 code without addressing these two breaking changes will result in build failures or non-functioning middleware. This guide walks through each change step by step, from project creation to migration, all in TypeScript.

Next.js 16 Key Breaking Changes Summary

The upgrade to Next.js 16 introduces two backward-incompatible changes. Whether upgrading an existing project or starting fresh, these need to be addressed first.

The first is the middleware.ts → proxy.ts transition. The exported function name also changed from middleware to proxy. proxy.ts runs on the Node.js runtime, and the old middleware.ts is now deprecated as an Edge-runtime-only construct. Any code that handled authentication checks or redirects in middleware.ts must have both the filename and function name updated to work properly.

The second is the Turbopack default bundler switch. Previously, Turbopack required the next dev --turbopack flag to opt in, but starting with Next.js 16, running next dev alone activates Turbopack. The catch is that projects with custom webpack configurations will fail to build. To continue using webpack, the --webpack flag must be explicitly specified, and the Turbopack configuration location has moved from experimental.turbopack to a top-level turbopack key.

Item Next.js 15 Next.js 16
Middleware file middleware.ts proxy.ts
Middleware function name middleware proxy
Middleware runtime Edge Node.js
Default bundler webpack Turbopack
webpack usage Default --webpack flag required
Turbopack config location experimental.turbopack Top-level turbopack
Default caching behavior Implicit caching Dynamic by default (opt-in caching)

Larger teams with extensive webpack customizations will feel the impact of the Turbopack switch more heavily. Smaller projects with minimal webpack dependencies will find the transition relatively straightforward. The Next.js 16 upgrade guide provides a full migration checklist.

Required Check for Existing middleware.ts Users
middleware.ts is in a deprecated state. It may still work for now, but it’s slated for removal in a future release. Both new and existing projects should migrate to proxy.ts. The filename and function name must both be changed.

Creating a Next.js 16 Project and Initial Setup

A Next.js 16 project is created with create-next-app. The system requirements are Node.js 20.9 or higher, with browser support for Chrome 111+, Edge 111+, Firefox 111+, and Safari 16.4+. Adding the --yes flag instantly generates a project with TypeScript, Tailwind CSS, ESLint, App Router, and Turbopack included by default.

npx create-next-app@latest my-app --yes
cd my-app
npm run dev

This single command launches a Turbopack-based dev server immediately. The separate --turbopack flag is no longer needed. The generated project’s package.json scripts section has the following structure.

{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  }
}

In previous versions, the dev script was explicitly set to "dev": "next dev --turbopack", but in Next.js 16 that flag is unnecessary. Conversely, only projects that need webpack specify "dev": "next dev --webpack".

Check Node.js Version
Running create-next-app on Node.js versions below 20.9 will fail outright. Verify the version with `node -v` before proceeding. nvm users can switch with `nvm use 20`.

Exploring the TypeScript Project Structure

A project generated with the --yes flag has TypeScript enabled by default. A tsconfig.json is auto-generated, and .tsx files are placed under the app/ directory.

my-app/
├── app/
│   ├── layout.tsx      ← Root layout
│   ├── page.tsx        ← Main page
│   └── globals.css
├── public/
├── next.config.ts      ← TypeScript config file
├── tsconfig.json
└── package.json

The notable file here is next.config.ts. The TypeScript-based configuration file introduced in Next.js 15 remains the default in Next.js 16. Using the .ts extension instead of next.config.js enables automatic type inference on the config object, so invalid keys trigger editor errors immediately.

proxy.ts Migration — Replacing middleware.ts

proxy.ts migration diagram

This is the most impactful breaking change in Next.js 16. middleware.ts has been replaced by proxy.ts, and the exported function name must change from middleware to proxy. Since proxy.ts runs on the Node.js runtime, the constraints of the Edge-only runtime are gone.

Converting a redirect handler from the old middleware.ts to proxy.ts results in the following pattern.

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

Apart from the function name changing to proxy, the NextRequest and NextResponse APIs remain identical. However, since the runtime has switched to Node.js, native Node.js modules (fs, crypto, etc.) that were previously unavailable under Edge runtime constraints can now be used directly inside proxy.ts.

Defining Proxy Response Types with TypeScript Interfaces

When proxy.ts handles complex branching logic, defining response types with interfaces makes maintenance easier. In large-scale projects, redirect targets can grow to dozens of entries, and working without types leads to accumulated mistakes.

interface ProxyResult {
  readonly destination: string;
  readonly statusCode: 301 | 302 | 307 | 308;
}

interface RouteConfig {
  readonly pattern: RegExp;
  readonly result: ProxyResult;
}

const routes: readonly RouteConfig[] = [
  {
    pattern: /^\/old-path/,
    result: { destination: '/new-path', statusCode: 301 },
  },
  {
    pattern: /^\/legacy/,
    result: { destination: '/modern', statusCode: 308 },
  },
] as const;

Using readonly and as const prevents accidental runtime mutations of the routes array at compile time. This may look like over-engineering for small projects, but once redirect rules exceed 20 entries, these type definitions noticeably reduce debugging time.

Note on Edge Runtime Alternatives
Specific alternatives for using Edge runtime with proxy.ts are not yet documented in the official docs. As of now, using proxy.ts on the Node.js runtime is the default path.

Turbopack as Default and Next.js 16 Build Configuration

Turbopack default build pipeline

In Next.js 16, Turbopack is the default bundler for both next dev and next build. The previous --turbopack opt-in flag is gone — running either command without flags now uses Turbopack.

The most critical point: builds will fail if custom webpack configurations exist. Projects with a webpack function defined in next.config.ts will encounter errors the moment they upgrade to Next.js 16. There are two options.

Option 1: Keep webpack — Explicitly pass the next dev --webpack and next build --webpack flags. Existing configurations continue to work, but Turbopack’s build speed advantages are forfeited.

Option 2: Migrate to Turbopack — Convert custom webpack configurations to Turbopack-compatible equivalents. However, detailed migration guides for custom webpack configurations are still limited in the official documentation.

Turbopack Configuration Location Change

The configuration location has also changed. It moved from experimental.turbopack to a top-level turbopack key. Projects that used Turbopack settings in Next.js 15 must update this path.

// Next.js 15 (before)
const oldConfig = {
  experimental: {
    turbopack: {
      // settings
    },
  },
};

// Next.js 16 (current)
const nextConfig = {
  turbopack: {
    // settings moved to top level
  },
};

export default nextConfig;

For larger projects, CI/CD pipeline build scripts should also be reviewed. The build cache structure may change when next build runs with Turbopack. Smaller projects often work fine with the defaults without any additional configuration.

Scenario Recommended Bundler Reason
New project Turbopack (default) No extra config needed, build speed benefits
Projects depending on webpack plugins webpack (--webpack) Plugin compatibility takes priority
Projects relying on CI/CD cache Gradual migration Verify cache structure impact before switching

Cache Components and the "use cache" Directive

Next.js 16 introduces Cache Components. The "use cache" directive enables opt-in caching at the page, component, or function level. This is fundamentally different from the App Router’s previous implicit caching approach — all dynamic code now executes at request time by default, and only the parts that need caching are explicitly declared.

Activation requires setting cacheComponents: true in next.config.ts.

const nextConfig = {
  cacheComponents: true,
};

export default nextConfig;

After enabling this setting, adding the "use cache" directive to individual components or functions marks them as cache targets. In Next.js 15, fetch requests were implicitly cached, frequently causing confusion about why data wasn’t updating. Next.js 16 resolves this issue structurally.

Designing Cache Utility Types with Generics

When introducing Cache Components into a TypeScript project, wrapping the return types of cacheable functions with generics is a useful pattern.

type CacheableResponse<T> = {
  readonly data: T;
  readonly cachedAt: string;
  readonly ttl: number;
};

type ApiEndpoint<TParams, TResult> = (
  params: TParams
) => Promise<CacheableResponse<TResult>>;

interface UserProfile {
  id: string;
  name: string;
  email: string;
}

interface UserQueryParams {
  userId: string;
}

With these type definitions in place, adding more cacheable functions still enforces a consistent response structure. The ttl field in CacheableResponse<T> makes cache lifetime explicit, enabling cache strategy tracking at the code level when used alongside the "use cache" directive.

Cache Components Migration Reference
Detailed examples for real-world Cache Components (`”use cache”`) migration patterns are still limited in the official documentation. For now, starting with simple data-fetching functions and deferring complex component tree caching until the feature stabilizes is the safer approach.

Additionally, revalidateTag() in Next.js 16 now requires a cacheLife profile as its second argument. A new updateTag() API has also been added, providing read-your-writes semantics — meaning updated values can be read immediately within the same request after cache invalidation. The Next.js 16 release blog covers the design intent and API details of Cache Components.

React 19.2 and React Compiler Setup

Next.js 16 ships with React 19.2. View Transitions, useEffectEvent, and the Activity component are newly supported. The most practical change is that React Compiler has been promoted to stable.

Activation requires just one line in next.config.ts: reactCompiler: true.

const nextConfig = {
  reactCompiler: true,
};

export default nextConfig;

React Compiler performs automatic memoization. Code that previously required manual wrapping with React.memo, useMemo, and useCallback is now optimized automatically by the compiler. With this feature reaching stable status, it can be confidently used in production.

Patterns Where React Compiler Shines in TypeScript

There are patterns where React Compiler’s automatic memoization is particularly effective. Generic components that required manual props comparison are a prime example.

interface DataTableProps<T> {
  readonly items: readonly T[];
  readonly renderRow: (item: T) => React.ReactNode;
  readonly keyExtractor: (item: T) => string;
}

function DataTable<T>({
  items,
  renderRow,
  keyExtractor,
}: DataTableProps<T>) {
  return (
    <table>
      <tbody>
        {items.map((item) => (
          <tr key={keyExtractor(item)}>
            <td>{renderRow(item)}</td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}

Previously, wrapping such generic components with React.memo sometimes required writing custom comparison functions. With React Compiler enabled, this boilerplate becomes unnecessary. Combined with the readonly modifier, immutability is guaranteed at the type level, achieving both compiler optimization and type safety simultaneously.

View Transitions and Activity Component
View Transitions in React 19.2 handle page transition animations, while the Activity component manages inactive UI state. Both features are available in Next.js 16, but integration patterns with App Router are still being established.

Next.js 16 TypeScript Configuration Optimization

Getting the most out of TypeScript in a Next.js 16 project requires checking a few configuration points. Since next.config.ts is a TypeScript file, type inference on the config object itself is the starting point.

Strict Mode and Next.js Type Integration

In tsconfig.json, strict: true is enabled by default. Adding Next.js 16-specific settings on top of this raises type safety another level.

// Leveraging type imports in next.config.ts
import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
  cacheComponents: true,
  reactCompiler: true,
};

export default nextConfig;

Explicitly importing the NextConfig type enables autocompletion for new Next.js 16 options like cacheComponents and reactCompiler. Entering a non-existent key triggers a compilation error, preventing configuration omissions caused by typos.

In large monorepo environments, extending the NextConfig type to create a shared team configuration interface is common. For smaller projects, the base NextConfig type is usually sufficient.

interface TeamNextConfig extends NextConfig {
  readonly customAnalytics?: {
    readonly provider: 'ga4' | 'amplitude';
    readonly trackingId: string;
  };
}

This kind of extension lets next.config.ts double as a team convention enforcer, where the type checker performs first-pass validation during config reviews.

Type-Safe Route Matching in proxy.ts

When path matching logic in proxy.ts grows complex, combining union types and generics for route definitions helps.

type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';

interface ProxyRule<T extends string = string> {
  readonly path: T;
  readonly methods: readonly HttpMethod[];
  readonly rewrite: (url: URL) => URL;
}

function createProxyRules<T extends string>(
  rules: readonly ProxyRule<T>[]
): ReadonlyMap<T, ProxyRule<T>> {
  return new Map(rules.map((r) => [r.path, r]));
}

In this pattern, the T extends string generic narrows path strings to literal types. Paths passed to createProxyRules propagate directly as ReadonlyMap key types, so accessing a non-existent path triggers a type error.

Next Steps — Deployment and Advanced Features

With the basic Next.js 16 setup complete, the next area to explore is deployment configuration. For Vercel deployments, the first step is verifying that Turbopack builds run by default. For self-hosted environments, the Node.js runtime version for next start must be 20.9 or higher.

The Next.js 16 installation guide covers all system requirements for production deployment. Before applying Cache Components’ "use cache" directive in production, testing the interaction with CDN cache layers is advisable.

Beyond that, configuring custom build pipelines in a Next.js 16 Turbopack environment is the next step. Bundle analysis, environment variable injection, CSS module handling, and other webpack-plugin-dependent processes need to be converted to Turbopack-compatible alternatives. Implementing authentication and authorization checks in Next.js 16 proxy.ts — leveraging Node.js runtime capabilities — is another area that becomes immediately relevant in practice. Combining Next.js 16 Cache Components with React 19.2’s View Transitions for page transition optimization extends beyond this starter guide into production-grade territory.

Scroll to Top