Skip to content

Web Development

Next.js 15: Complete Guide to Migrating Your Site in 2026

Next.js 15 brings major changes. Discover our step-by-step guide to migrate your application without service interruption.

By Lenobot Engineering

  • 14 min read
Next.js 15: Complete Guide to Migrating Your Site in 2026

Why Migrate to Next.js 15 in 2026

Next.js 15 represents a major evolution of the most popular React framework. With 40% performance gains, improved DX (Developer Experience), and AI-oriented features, migration is a strategic investment.

Performance: Next.js 15 with Turbopack is 2.4x faster at build time and 40% faster at runtime than Next.js 13 (Vercel Labs benchmark).

Major New Features in Next.js 15

  • Stable Turbopack: ultra-fast Rust compiler, Webpack replacement
  • Native React 19: Server Components, Actions, use() hook
  • Partial Prerendering (PPR): SSG + streaming SSR combination
  • Improved caching: granular cache control with staleTimes
  • Enhanced Image component: automatic AVIF/WebP optimization
  • Middleware V2: more powerful and flexible Edge Middleware

Prerequisites and Preparation

Compatibility Audit

Before starting, evaluate your current application:

# Check your current version
npx next --version

# Run the automatic migration tool
npx @next/codemod@latest upgrade

# Analyze incompatible dependencies
npx npm-check-updates --filter "/next|react/"

Pre-Migration Checklist

  • Node.js 20+ installed
  • All dependencies up to date
  • Unit and E2E tests pass
  • Database backup
  • Dedicated Git branch for migration
  • Staging environment ready

Dependency Compatibility

Check these common libraries:

LibraryNext.js 15 CompatibleAction Required
next-authv5+Migrate to Auth.js
next-intlv4+Update
@tanstack/react-queryv5+Compatible
next-seoDeprecatedUse Metadata API
styled-componentsv6+Configure SWC
tailwindcssv4+Compatible

Step-by-Step Migration

Step 1: Update Dependencies

# Update Next.js and React
pnpm add next@15 react@19 react-dom@19

# Update TypeScript types
pnpm add -D @types/react@19 @types/react-dom@19

# Update ESLint
pnpm add -D eslint-config-next@15

Step 2: Configure Turbopack

Replace your Webpack configuration with Turbopack:

// next.config.ts (new TypeScript format)
import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
  // Turbopack is now the default bundler
  // No more experimental.turbo needed

  images: {
    formats: ['image/avif', 'image/webp'],
    remotePatterns: [
      {
        protocol: 'https',
        hostname: '**.example.com',
      },
    ],
  },

  // New: granular cache control
  experimental: {
    staleTimes: {
      dynamic: 30, // seconds
      static: 180,
    },
    ppr: true, // Partial Prerendering
  },
};

export default nextConfig;

Step 3: Migrate to App Router (if not done)

If you're still on the Pages Router, now is the time to migrate:

Before (Pages Router):

// pages/blog/[slug].tsx
export async function getStaticProps({ params }) {
  const post = await getPost(params.slug);
  return { props: { post }, revalidate: 60 };
}

export default function BlogPost({ post }) {
  return <article>{post.title}</article>;
}

After (App Router):

// app/blog/[slug]/page.tsx
async function getPost(slug: string) {
  const res = await fetch(\`/api/posts/\${slug}\`, {
    next: { revalidate: 60 }
  });
  return res.json();
}

export default async function BlogPost({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;
  const post = await getPost(slug);
  return <article>{post.title}</article>;
}

Step 4: Adapt Server Components

Next.js 15 makes Server Components the default. Explicitly mark interactive components:

// Server component (default), no directive needed
export default async function ProductList() {
  const products = await db.products.findMany();

  return (
    <div>
      {products.map(p => (
        <ProductCard key={p.id} product={p} />
      ))}
      {/* Client component for interactivity */}
      <AddToCartButton />
    </div>
  );
}
// Client component, directive required
'use client';

import { useState } from 'react';

export function AddToCartButton() {
  const [count, setCount] = useState(0);
  return (
    <button onClick={() => setCount(c => c + 1)}>
      Add ({count})
    </button>
  );
}

Step 5: Migrate Server Actions

Next.js 15 stabilizes Server Actions for data mutations:

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

import { z } from 'zod';

const contactSchema = z.object({
  name: z.string().min(2),
  email: z.string().email(),
  message: z.string().min(10),
});

export async function submitContact(formData: FormData) {
  const data = contactSchema.parse({
    name: formData.get('name'),
    email: formData.get('email'),
    message: formData.get('message'),
  });

  await db.contacts.create({ data });

  return { success: true };
}

Step 6: Enable Partial Prerendering (PPR)

PPR is the most exciting feature of Next.js 15:

// app/products/page.tsx
import { Suspense } from 'react';

// Static part is pre-rendered at build
export default function ProductsPage() {
  return (
    <div>
      <h1>Our Products</h1>
      <StaticProductGrid /> {/* SSG */}

      {/* Dynamic part is streamed */}
      <Suspense fallback={<PriceSkeleton />}>
        <DynamicPrices /> {/* SSR streaming */}
      </Suspense>

      <Suspense fallback={<CartSkeleton />}>
        <UserCart /> {/* SSR streaming */}
      </Suspense>
    </div>
  );
}

Handling Breaking Changes

Async API Changes

Next.js 15 makes certain APIs asynchronous:

// BEFORE (Next.js 14)
import { cookies, headers } from 'next/headers';

function OldWay() {
  const cookieStore = cookies();
  const headersList = headers();
}

// AFTER (Next.js 15)
async function NewWay() {
  const cookieStore = await cookies();
  const headersList = await headers();
}

Default Cache Behavior Change

// BEFORE: fetch was cached by default
fetch('/api/data'); // cached indefinitely

// AFTER: fetch is no longer cached by default
fetch('/api/data'); // no cache
fetch('/api/data', { cache: 'force-cache' }); // explicit cache
fetch('/api/data', { next: { revalidate: 3600 } }); // ISR

Testing and Validation

Post-Migration Test Strategy

  1. Unit tests: verify all components render correctly
  2. E2E tests: walk through all critical user flows
  3. Performance tests: compare before/after metrics
  4. SEO tests: verify SSR rendering and meta tags
# Run tests
pnpm test

# Verify build
pnpm build

# Analyze bundle
npx @next/bundle-analyzer

Metrics to Compare

MetricBeforeTarget After
Build timeBaseline-40%
LCPBaseline-30%
FID/INPBaseline-50%
Bundle sizeBaseline-20%
TTFBBaseline-25%

Zero-Downtime Deployment

Blue-Green Strategy

  1. Deploy Next.js 15 version to a parallel environment
  2. Run automated smoke tests
  3. Gradually switch traffic (10% -> 50% -> 100%)
  4. Keep old environment ready for rollback

Conclusion: The Migration Is Worth It

Migrating to Next.js 15 is an investment that pays off quickly: improved performance, superior DX, and access to cutting-edge features like PPR and Server Actions.

Need help with your migration? Lenobot is a Next.js expert and helps businesses with zero-downtime migrations. Contact us to plan your migration.

Written by

Lenobot Engineering

We build Next.js websites, SEO and GEO strategies and AI agents connected to our clients’ business tools.

Meet the team

Web & AI engineering

A website and AI agents that bring you clients

Book a 30-minute diagnostic with our team to map your fastest growth levers.

Next.js 15 Migration in 2026: Complete Step-by-Step Guide | Lenobot