Trizen Labs Logo
Blog
TrizenLabs

We are product engineers who push technology's limits to fuel business growth and innovation. Let us guide your idea from design to launch.

hello@trizenlabs.com
Building the future, remotely

Services

Company

  • Blog

Resources

  • Latest Articles
  • Tech Insights

© 2024 TrizenLabs. All rights reserved.

PrivacyTermsCookies
  1. Home
  2. Blog
  3. Next.js 15: What's New and How to Migrate
Next.jsMigrationPerformanceReactJavaScript

Next.js 15: What's New and How to Migrate

Discover the latest features in Next.js 15 and learn how to smoothly migrate your existing applications to take advantage of improved performance and developer experience.

Alex Rodriguez
July 28, 2025
7 min read
Next.js 15: What's New and How to Migrate

Next.js 15: What's New and How to Migrate

Next.js 15 brings significant improvements in performance, developer experience, and new features that make building full-stack React applications even more enjoyable. Let's explore what's new and how you can migrate your existing projects.

What's New in Next.js 15

1. Improved App Router

The App Router, introduced in Next.js 13, receives major stability and performance improvements:

  • Faster Navigation: Client-side navigation is now 30% faster
  • Better Error Handling: Enhanced error boundaries and recovery mechanisms
  • Improved Streaming: Better support for progressive enhancement

2. Enhanced Turbopack

Turbopack, the Rust-based bundler, is now stable for development:

# Enable Turbopack for development
npm run dev -- --turbo

# Or add to package.json
{
  "scripts": {
    "dev": "next dev --turbo"
  }
}

Performance improvements:

  • 70% faster cold starts
  • 5x faster incremental builds
  • Reduced memory usage

3. React 19 Support

Next.js 15 fully supports React 19 features:

// Server Components with async/await
async function UserProfile({ userId }: { userId: string }) {
  const user = await fetchUser(userId);

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.bio}</p>
    </div>
  );
}

// New use() hook for data fetching
function UserPosts({ userPromise }: { userPromise: Promise<User> }) {
  const user = use(userPromise);

  return (
    <div>
      {user.posts.map(post => (
        <PostCard key={post.id} post={post} />
      ))}
    </div>
  );
}

4. Partial Prerendering (PPR)

Revolutionary rendering strategy that combines static and dynamic content:

// app/product/[id]/page.tsx
import { Suspense } from 'react';

export default function ProductPage({ params }: { params: { id: string } }) {
  return (
    <div>
      {/* Static: Prerendered at build time */}
      <ProductHeader productId={params.id} />

      {/* Dynamic: Rendered on demand */}
      <Suspense fallback={<ReviewsSkeleton />}>
        <ProductReviews productId={params.id} />
      </Suspense>

      {/* Dynamic: User-specific content */}
      <Suspense fallback={<RecommendationsSkeleton />}>
        <PersonalizedRecommendations />
      </Suspense>
    </div>
  );
}

// Enable PPR
// next.config.js
module.exports = {
  experimental: {
    ppr: true,
  },
};

5. Improved Caching

Enhanced caching strategies with better control:

// app/api/posts/route.ts
import { NextRequest, NextResponse } from 'next/server';

export async function GET(request: NextRequest) {
  const posts = await fetchPosts();

  return NextResponse.json(posts, {
    headers: {
      'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=86400',
    },
  });
}

// Fine-grained cache control
export const revalidate = 3600; // Revalidate every hour
export const dynamic = 'force-static'; // Force static generation

6. Server Actions Improvements

Enhanced Server Actions with better type safety and error handling:

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

import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";

// app/create-post/page.tsx
import { createPost } from "../actions";

// app/actions.ts

export async function createPost(formData: FormData) {
  const title = formData.get("title") as string;
  const content = formData.get("content") as string;

  try {
    const post = await db.post.create({
      data: { title, content },
    });

    revalidatePath("/blog");
    redirect(`/blog/${post.slug}`);
  } catch (error) {
    return { error: "Failed to create post" };
  }
}

export default function CreatePost() {
  return (
    <form action={createPost}>
      <input name="title" placeholder="Post title" required />
      <textarea name="content" placeholder="Post content" required />
      <button type="submit">Create Post</button>
    </form>
  );
}

Migration Guide

Step 1: Update Dependencies

npm install next@15 react@19 react-dom@19
npm install @types/react@19 @types/react-dom@19 # If using TypeScript

Step 2: Update Next.js Configuration

// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  // Enable new features
  experimental: {
    ppr: true, // Partial Prerendering
    reactCompiler: true, // React Compiler
  },

  // Remove deprecated options
  // swcMinify: true, // Now default, remove this line
};

module.exports = nextConfig;

Step 3: Update TypeScript Configuration

{
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["dom", "dom.iterable", "es6"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "plugins": [
      {
        "name": "next"
      }
    ],
    "paths": {
      "@/*": ["./*"]
    }
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
  "exclude": ["node_modules"]
}

Step 4: Migrate Pages to App Router (If Not Done)

If you're still using the Pages Router, consider migrating to App Router:

// pages/blog/[slug].tsx (Old)
export default function BlogPost({ post }) {
  return <div>{post.title}</div>;
}

export async function getStaticProps({ params }) {
  const post = await fetchPost(params.slug);
  return { props: { post } };
}

// app/blog/[slug]/page.tsx (New)
interface PageProps {
  params: { slug: string };
}

export default async function BlogPost({ params }: PageProps) {
  const post = await fetchPost(params.slug);

  return <div>{post.title}</div>;
}

export async function generateStaticParams() {
  const posts = await fetchAllPosts();
  return posts.map(post => ({ slug: post.slug }));
}

Step 5: Update Error Handling

// app/error.tsx
'use client';

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  return (
    <div>
      <h2>Something went wrong!</h2>
      <button onClick={() => reset()}>Try again</button>
    </div>
  );
}

// app/global-error.tsx
('use client');

export default function GlobalError({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  return (
    <html>
      <body>
        <h2>Something went wrong!</h2>
        <button onClick={() => reset()}>Try again</button>
      </body>
    </html>
  );
}

Step 6: Optimize Images and Assets

// Use the new Image component optimizations
import Image from 'next/image';

export default function Gallery() {
  return (
    <div>
      <Image
        src="/hero.jpg"
        alt="Hero image"
        width={800}
        height={600}
        priority // Load above the fold images first
        placeholder="blur" // Show blur while loading
        blurDataURL="data:image/jpeg;base64,..." // Blur placeholder
      />
    </div>
  );
}

Breaking Changes to Watch

1. Minimum Node.js Version

  • Node.js 18.17 is now the minimum required version

2. Deprecated APIs Removed

// ❌ Removed in Next.js 15
import { useRouter } from 'next/router'; // Pages Router only

// ✅ Use App Router navigation
import { useRouter } from 'next/navigation';

3. Changed Default Behaviors

// ❌ Old behavior: fetch requests were cached by default
fetch('/api/data'); // Was cached

// ✅ New behavior: opt-in caching
fetch('/api/data', { cache: 'force-cache' }); // Explicitly cached
fetch('/api/data', { next: { revalidate: 3600 } }); // Revalidated

Performance Optimizations

1. Bundle Analysis

# Analyze bundle size
npm install -g @next/bundle-analyzer
ANALYZE=true npm run build

2. Code Splitting Improvements

// Dynamic imports with better loading states
import dynamic from 'next/dynamic';

const DynamicComponent = dynamic(() => import('../components/HeavyComponent'), {
  loading: () => <ComponentSkeleton />,
  ssr: false, // Disable SSR for client-only components
});

3. Streaming and Suspense

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

export default function Dashboard() {
  return (
    <div>
      <h1>Dashboard</h1>

      <Suspense fallback={<ChartSkeleton />}>
        <AnalyticsChart />
      </Suspense>

      <Suspense fallback={<TableSkeleton />}>
        <DataTable />
      </Suspense>
    </div>
  );
}

Testing Your Migration

1. Performance Testing

# Lighthouse CI
npm install -g @lhci/cli
lhci autorun

# Core Web Vitals monitoring
npm install web-vitals

2. Bundle Size Comparison

# Before migration
npm run build
npm run analyze

# After migration (compare results)
npm run build
npm run analyze

Conclusion

Next.js 15 represents a significant leap forward in React framework capabilities. The improved performance, better developer experience, and new features like Partial Prerendering make it a compelling upgrade for any React application.

Migration checklist:

  • ✅ Update dependencies to Next.js 15 and React 19
  • ✅ Enable Turbopack for development
  • ✅ Migrate to App Router (if applicable)
  • ✅ Update error handling
  • ✅ Test performance improvements
  • ✅ Leverage new caching strategies