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.
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
- Unit tests: verify all components render correctly
- E2E tests: walk through all critical user flows
- Performance tests: compare before/after metrics
- 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
- Deploy Next.js 15 version to a parallel environment
- Run automated smoke tests
- Gradually switch traffic (10% -> 50% -> 100%)
- 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