Lenobot
Back to blog

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.

April 8, 202614 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:

| Library | Next.js 15 Compatible | Action Required | |---|---|---| | next-auth | v5+ | Migrate to Auth.js | | next-intl | v4+ | Update | | @tanstack/react-query | v5+ | Compatible | | next-seo | Deprecated | Use Metadata API | | styled-components | v6+ | Configure SWC | | tailwindcss | v4+ | 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

| Metric | Before | Target After | |---|---|---| | Build time | Baseline | -40% | | LCP | Baseline | -30% | | FID/INP | Baseline | -50% | | Bundle size | Baseline | -20% | | TTFB | Baseline | -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.

Need help with your project?

Our experts are ready to support you in your digital transformation.

Let's discuss your project

Related articles