Next.js 15 New Features - React 19 Support and Turbopack Stabilization

2025.12.02

Next.js 15 has been released with many innovative features including React 19 support and stable Turbopack. This article explains the major changes in Next.js 15 that developers need to know and how to migrate.

Key New Features Summary

FeatureStatusImpact
React 19 SupportStableHigh
Turbopack DevStableHigh
Partial Prerendering (PPR)ExperimentalMedium
next/after APIStableMedium
Cache Behavior ChangesBreakingHigh
ESLint 9 SupportStableLow

React 19 Support

Integration with React 19 RC

Next.js 15 is tightly integrated with React 19.

# Create new project
npx create-next-app@latest my-app

# Upgrade existing project
npm install next@latest react@rc react-dom@rc

Enhanced Actions

React 19’s useActionState and useFormStatus are now natively supported.

// app/actions.ts
'use server';

export async function submitForm(prevState: any, formData: FormData) {
  const email = formData.get('email');

  // Validation
  if (!email || typeof email !== 'string') {
    return { error: 'Please enter a valid email address' };
  }

  // Save to database
  await saveToDatabase(email);

  return { success: true, message: 'Registration complete' };
}
// app/form.tsx
'use client';

import { useActionState } from 'react';
import { useFormStatus } from 'react-dom';
import { submitForm } from './actions';

function SubmitButton() {
  const { pending } = useFormStatus();
  return (
    <button type="submit" disabled={pending}>
      {pending ? 'Submitting...' : 'Register'}
    </button>
  );
}

export function NewsletterForm() {
  const [state, formAction] = useActionState(submitForm, null);

  return (
    <form action={formAction}>
      <input type="email" name="email" placeholder="Email address" />
      <SubmitButton />
      {state?.error && <p className="error">{state.error}</p>}
      {state?.success && <p className="success">{state.message}</p>}
    </form>
  );
}

React Compiler (Experimental)

React Compiler eliminates the need for manual useMemo and useCallback.

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

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

export default nextConfig;
# Install Babel plugin
npm install babel-plugin-react-compiler

Turbopack Dev Stable

Dramatic Performance Improvement

Turbopack is now stable for development mode (next dev).

# Start dev server with Turbopack enabled
next dev --turbo

Benchmark Results

Next.js 15 + Turbopack vs Webpack:

MetricWebpackTurbopack
Local server startup4.2s1.1s
Initial compile8.5s2.3s
Fast Refresh320ms45ms
Route update180ms25ms

Measured on large application (1000+ components)

Support Status

// Current Turbopack support status
const turbopackSupport = {
  development: 'stable',      // Stable
  production: 'coming soon',  // Coming soon
  features: {
    appRouter: 'full',
    pagesRouter: 'full',
    cssModules: 'full',
    tailwindCSS: 'full',
    sassScss: 'full',
    mdx: 'full',
    nextImage: 'full',
    nextFont: 'full',
  },
};

Partial Prerendering (PPR)

Optimal Combination of Static and Dynamic

PPR is an experimental feature that efficiently combines static and dynamic parts on the same page.

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

const nextConfig: NextConfig = {
  experimental: {
    ppr: 'incremental',  // Enable per route
  },
};

export default nextConfig;
// app/dashboard/page.tsx
import { Suspense } from 'react';
import { StaticHeader } from './static-header';
import { DynamicContent } from './dynamic-content';

// Enable PPR
export const experimental_ppr = true;

export default function Dashboard() {
  return (
    <div>
      {/* Statically prerendered */}
      <StaticHeader />

      {/* Dynamically streamed */}
      <Suspense fallback={<LoadingSkeleton />}>
        <DynamicContent />
      </Suspense>
    </div>
  );
}

PPR Flow

flowchart TB
    subgraph BuildTime["At Build Time"]
        StaticShell["Static Shell (Header, Layout, Fallback)<br/>→ Pre-generated as HTML"]
    end

    subgraph RequestTime["At Request Time"]
        Step1["1. Return static shell immediately (minimize TTFB)"]
        Step2["2. Stream dynamic content"]
        Step3["3. Progressive hydration at Suspense boundaries"]
        Step1 --> Step2 --> Step3
    end

    BuildTime --> RequestTime

next/after API

Execute Processing After Response

next/after is a new API that executes processing after sending the response to the client.

// app/api/submit/route.ts
import { after } from 'next/server';

export async function POST(request: Request) {
  const data = await request.json();

  // Return response immediately
  const result = await processSubmission(data);

  // Background processing after response
  after(async () => {
    await sendEmailNotification(data.email);
    await logAnalytics('form_submitted', data);
    await updateSearchIndex(result.id);
  });

  return Response.json({ success: true, id: result.id });
}
// Usage in Server Component
import { after } from 'next/server';

export default async function Page() {
  const data = await fetchData();

  // Execute after page rendering
  after(() => {
    logPageView('/dashboard');
  });

  return <Dashboard data={data} />;
}

Use Cases

  • Analytics submission
  • Logging
  • Cache warming
  • Sending notifications
  • Search index updates

Cache Behavior Changes

Default Behavior Changes (Breaking Change)

In Next.js 15, the default caching behavior has significantly changed.

// Before Next.js 14
fetch('https://api.example.com/data');
// Default: force-cache (cached)

// Next.js 15
fetch('https://api.example.com/data');
// Default: no-store (not cached)

Explicit Cache Specification

// Explicitly specify to enable caching
fetch('https://api.example.com/data', {
  cache: 'force-cache',
});

// Or specify revalidate
fetch('https://api.example.com/data', {
  next: { revalidate: 3600 },  // 1 hour
});

Route Handler Caching

// app/api/data/route.ts

// Next.js 14: GET was cached by default
// Next.js 15: No cache by default

export async function GET() {
  const data = await fetchData();
  return Response.json(data);
}

// To make it static
export const dynamic = 'force-static';

// Or specify revalidate
export const revalidate = 3600;

Client Router Cache

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

const nextConfig: NextConfig = {
  experimental: {
    staleTimes: {
      dynamic: 30,   // Cache time for dynamic pages (seconds)
      static: 180,   // Cache time for static pages (seconds)
    },
  },
};

Instrumentation API Stabilization

Application Bootstrap

// instrumentation.ts (project root)
export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    // Execute only in Node.js runtime
    const { initializeDatabase } = await import('./lib/db');
    await initializeDatabase();

    const { startMetricsCollection } = await import('./lib/metrics');
    startMetricsCollection();
  }

  if (process.env.NEXT_RUNTIME === 'edge') {
    // Initialization in Edge runtime
    console.log('Edge runtime initialized');
  }
}

export async function onRequestError(
  err: Error,
  request: Request,
  context: { routerKind: string; routePath: string }
) {
  // Error tracking
  await reportError(err, {
    url: request.url,
    ...context,
  });
}

Developer Experience Improvements

Improved Error Display

flowchart TB
    subgraph ErrorUI["Next.js 15 Error UI Improvements"]
        Error["Error: Cannot read properties of undefined"]

        subgraph SourceCode["Source code"]
            Line12["12 │ const user = await getUser(id);"]
            Line13["13 │ return user.name; ← Error here"]
            Line14["14 │ }"]
        end

        subgraph StackTrace["Stack trace"]
            ST1["app/dashboard/page.tsx:13"]
            ST2["..."]
        end

        Actions["[View docs] [Report issue]"]

        Error --> SourceCode --> StackTrace --> Actions
    end

Static Indicator

Visually confirm static/dynamic rendering during development.

// Static pages show green indicator
// Dynamic pages show blue indicator
// displayed in bottom right of browser

Upgrade Method

Automatic Upgrade

# Automatic upgrade using codemod
npx @next/codemod@canary upgrade latest

Manual Upgrade

# Update dependencies
npm install next@latest react@rc react-dom@rc

# For TypeScript
npm install @types/react@rc @types/react-dom@rc

Main Migration Tasks

// 1. fetch cache behavior
// Before (Next.js 14)
fetch('/api/data');  // Cached

// After (Next.js 15)
fetch('/api/data', { cache: 'force-cache' });  // Explicitly specify

// 2. Route Handler
// Before
export async function GET() { ... }  // Auto-cached

// After
export const dynamic = 'force-static';  // Explicitly make static
export async function GET() { ... }

// 3. Server Actions import
// Before
'use server';
// Entire file is Server Actions

// After (recommended)
// Separate into actions.ts and import
import { submitForm } from './actions';
my-app/
├── app/
│   ├── (auth)/
│   │   ├── login/
│   │   └── signup/
│   ├── (dashboard)/
│   │   ├── layout.tsx
│   │   └── page.tsx
│   ├── api/
│   │   └── [...route]/
│   ├── actions/          # Server Actions
│   │   └── form.ts
│   ├── layout.tsx
│   └── page.tsx
├── components/
│   ├── ui/
│   └── features/
├── lib/
│   ├── db.ts
│   └── utils.ts
├── instrumentation.ts   # New addition
├── next.config.ts       # From .mjs to .ts
└── package.json

Performance Optimization Tips

1. Leverage Turbopack

// package.json
{
  "scripts": {
    "dev": "next dev --turbo",
    "build": "next build",
    "start": "next start"
  }
}

2. Gradual PPR Adoption

// Enable sequentially starting with high-traffic pages
export const experimental_ppr = true;

3. Appropriate Caching Strategy

// Cache settings based on data characteristics
const staticData = await fetch('/api/config', {
  cache: 'force-cache',
});

const userData = await fetch('/api/user', {
  cache: 'no-store',  // Always fresh
});

const productData = await fetch('/api/products', {
  next: { revalidate: 60 },  // Update every minute
});

Summary

Next.js 15 represents significant evolution in both performance and developer experience.

Key Upgrade Points

  1. React 19 Support: New hooks, improved Server Actions
  2. Turbopack Stable: Dramatically faster build times during development
  3. PPR: Optimal combination of static and dynamic
  4. Cache Changes: More predictable default behavior

Migration Notes

  • fetch cache behavior defaults to no-store
  • Route Handler caching changed similarly
  • Recommend gradual migration using codemods

Combined with React 19, Next.js has become even more powerful as a full-stack React framework.

← Back to list