next 精选推荐

Next.js 15 新特性全解析:Turbopack 稳定版与 React Server Components 深度实践

HTMLPAGE 团队
18 分钟阅读

全面解读 Next.js 15 的重大更新,深入分析 Turbopack 生产环境稳定性、React Server Components 最佳实践、部分预渲染(PPR)、改进的缓存策略等核心特性。

#Next.js #React Server Components #Turbopack #App Router #全栈开发

Next.js 15 新特性全解析

概述:Next.js 15 的里程碑意义

Next.js 15 是 Vercel 团队在 2024 年底发布的重大版本,标志着 Next.js 从"React 框架"向"React 全栈平台"的成熟演进。经过一年多的生产环境验证,2026 年的 Next.js 15.x 已成为企业级应用的首选方案。

核心亮点

特性说明影响
Turbopack 稳定版开发环境编译速度提升 10x开发体验
React 19 集成use() hook、Server Actions 增强编程模型
部分预渲染(PPR)静态外壳 + 动态内容性能
改进的缓存机制更细粒度的缓存控制可预测性
TypeScript 增强更好的类型推断和提示开发效率

Turbopack:生产就绪的构建引擎

从实验到稳定

Turbopack 经历了近两年的迭代,终于在 Next.js 15 中达到生产级稳定。它是 Webpack 的 Rust 重写版本,由 Webpack 原作者 Tobias Koppers 领导开发。

性能对比:

┌─────────────────────────────────────────────────────────┐
│                    冷启动时间对比                        │
├─────────────────────────────────────────────────────────┤
│  Webpack 5    ████████████████████████████████  15.2s   │
│  Turbopack    ████                              1.8s    │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│                    热更新时间对比                        │
├─────────────────────────────────────────────────────────┤
│  Webpack 5    ████████████████                  1.2s    │
│  Turbopack    ██                                0.1s    │
└─────────────────────────────────────────────────────────┘

启用 Turbopack

# 开发环境(默认启用)
next dev --turbo

# 或在 next.config.ts 中配置
// next.config.ts
import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
  experimental: {
    // 开发环境默认启用,这里显式声明
    turbo: {
      // Turbopack 配置
      rules: {
        // 自定义加载器
        '*.svg': {
          loaders: ['@svgr/webpack'],
          as: '*.js',
        },
      },
      resolveAlias: {
        // 别名配置
        '@components': './src/components',
      },
      resolveExtensions: ['.tsx', '.ts', '.jsx', '.js'],
    },
  },
};

export default nextConfig;

Turbopack 与 Webpack 的兼容性

// 大多数 Webpack 配置可以直接迁移
// 但有些需要调整

// webpack.config.js 迁移前
module.exports = {
  module: {
    rules: [
      {
        test: /\.svg$/,
        use: ['@svgr/webpack'],
      },
    ],
  },
};

// next.config.ts 迁移后
const nextConfig: NextConfig = {
  experimental: {
    turbo: {
      rules: {
        '*.svg': {
          loaders: ['@svgr/webpack'],
          as: '*.js',
        },
      },
    },
  },
  
  // 仍然可以保留 webpack 配置作为后备
  webpack: (config, { isServer }) => {
    // Turbopack 不会执行这里的代码
    // 仅在 --no-turbo 时使用
    return config;
  },
};

React Server Components 深度实践

RSC 的核心理念

React Server Components (RSC) 不只是"在服务器渲染组件",而是一种全新的组件模型,将组件分为两类:

┌────────────────────────────────────────────────────────────┐
│                    React 组件模型                          │
├───────────────────────────┬────────────────────────────────┤
│     Server Components     │      Client Components         │
├───────────────────────────┼────────────────────────────────┤
│ - 在服务器执行            │ - 在客户端执行                 │
│ - 可以直接访问数据库      │ - 可以使用浏览器 API           │
│ - 可以使用服务端 API      │ - 可以使用状态和副作用         │
│ - 代码不发送到客户端      │ - 代码打包到客户端             │
│ - 没有客户端交互能力      │ - 支持事件处理                 │
└───────────────────────────┴────────────────────────────────┘

何时使用 Server vs Client Components

// ✅ 适合 Server Component 的场景
// app/products/page.tsx

// 默认就是 Server Component
export default async function ProductsPage() {
  // 直接访问数据库
  const products = await db.product.findMany();
  
  // 直接调用服务端 API
  const recommendations = await getRecommendations(products);
  
  return (
    <div>
      <ProductList products={products} />
      <Recommendations items={recommendations} />
    </div>
  );
}

// ✅ 需要 Client Component 的场景
// app/components/AddToCart.tsx
'use client';

import { useState } from 'react';
import { useCart } from '@/hooks/useCart';

export function AddToCart({ productId }: { productId: string }) {
  const [loading, setLoading] = useState(false);
  const { addItem } = useCart();
  
  const handleClick = async () => {
    setLoading(true);
    await addItem(productId);
    setLoading(false);
  };
  
  return (
    <button onClick={handleClick} disabled={loading}>
      {loading ? '添加中...' : '加入购物车'}
    </button>
  );
}

Server Component 数据获取模式

// 模式 1:直接在组件中获取数据
// app/dashboard/page.tsx
async function DashboardPage() {
  // 这些请求会并行执行(React 自动优化)
  const userPromise = getUser();
  const analyticsPromise = getAnalytics();
  const notificationsPromise = getNotifications();
  
  const [user, analytics, notifications] = await Promise.all([
    userPromise,
    analyticsPromise,
    notificationsPromise,
  ]);
  
  return (
    <Dashboard 
      user={user} 
      analytics={analytics} 
      notifications={notifications} 
    />
  );
}

// 模式 2:流式加载 with Suspense
// app/dashboard/page.tsx
import { Suspense } from 'react';

async function DashboardPage() {
  const user = await getUser(); // 关键数据先加载
  
  return (
    <div>
      <UserHeader user={user} />
      
      {/* 非关键数据流式加载 */}
      <Suspense fallback={<AnalyticsSkeleton />}>
        <Analytics />
      </Suspense>
      
      <Suspense fallback={<NotificationsSkeleton />}>
        <Notifications />
      </Suspense>
    </div>
  );
}

// 独立的数据获取组件
async function Analytics() {
  const data = await getAnalytics();
  return <AnalyticsChart data={data} />;
}

async function Notifications() {
  const items = await getNotifications();
  return <NotificationList items={items} />;
}

Server Actions:表单处理新范式

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

import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
import { z } from 'zod';

// 定义验证 schema
const updateProfileSchema = z.object({
  name: z.string().min(2).max(50),
  email: z.string().email(),
  bio: z.string().max(500).optional(),
});

// Server Action
export async function updateProfile(formData: FormData) {
  // 1. 验证数据
  const validatedFields = updateProfileSchema.safeParse({
    name: formData.get('name'),
    email: formData.get('email'),
    bio: formData.get('bio'),
  });
  
  if (!validatedFields.success) {
    return {
      errors: validatedFields.error.flatten().fieldErrors,
    };
  }
  
  // 2. 获取当前用户
  const session = await getSession();
  if (!session) {
    redirect('/login');
  }
  
  // 3. 更新数据库
  try {
    await db.user.update({
      where: { id: session.userId },
      data: validatedFields.data,
    });
  } catch (error) {
    return {
      message: '更新失败,请重试',
    };
  }
  
  // 4. 刷新缓存
  revalidatePath('/profile');
  
  return { success: true };
}

// app/profile/page.tsx
import { updateProfile } from '@/app/actions/user';

export default function ProfilePage() {
  return (
    <form action={updateProfile}>
      <input name="name" placeholder="姓名" />
      <input name="email" type="email" placeholder="邮箱" />
      <textarea name="bio" placeholder="个人简介" />
      <SubmitButton />
    </form>
  );
}

// 客户端组件处理加载状态
'use client';
import { useFormStatus } from 'react-dom';

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

部分预渲染(Partial Prerendering)

PPR 的工作原理

PPR 是 Next.js 15 引入的革命性渲染模式,结合了静态生成和动态渲染的优点:

传统 SSR 流程:
请求 → 服务器渲染整个页面 → 返回完整 HTML
       (等待所有数据)

PPR 流程:
请求 → 立即返回静态外壳 → 流式填充动态内容
       (瞬时响应)         (按需加载)

启用 PPR

// next.config.ts
const nextConfig: NextConfig = {
  experimental: {
    ppr: true, // 启用 PPR
  },
};

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

export default async function ProductPage({ 
  params 
}: { 
  params: { id: string } 
}) {
  // 静态部分:布局、导航等
  return (
    <div>
      <Header /> {/* 静态 */}
      <Breadcrumb productId={params.id} /> {/* 静态 */}
      
      {/* 动态部分:使用 Suspense 边界 */}
      <Suspense fallback={<ProductSkeleton />}>
        <ProductDetails id={params.id} />
      </Suspense>
      
      <Suspense fallback={<ReviewsSkeleton />}>
        <ProductReviews id={params.id} />
      </Suspense>
      
      <Footer /> {/* 静态 */}
    </div>
  );
}

PPR 的边界定义

// 使用 loading.tsx 定义 PPR 边界
// app/dashboard/loading.tsx
export default function DashboardLoading() {
  return <DashboardSkeleton />;
}

// 或使用内联 Suspense
// 更细粒度的控制
function Dashboard() {
  return (
    <div className="grid grid-cols-3 gap-4">
      {/* 这部分是静态的 */}
      <Sidebar />
      
      {/* 这些是动态流式加载的 */}
      <main className="col-span-2">
        <Suspense fallback={<ChartSkeleton />}>
          <RevenueChart />
        </Suspense>
        
        <Suspense fallback={<TableSkeleton />}>
          <RecentOrders />
        </Suspense>
      </main>
    </div>
  );
}

改进的缓存机制

Next.js 15 的缓存变化

Next.js 15 对缓存策略进行了重大调整,默认不再缓存 fetch 请求

// Next.js 14 行为
fetch('https://api.example.com/data'); // 默认缓存

// Next.js 15 行为
fetch('https://api.example.com/data'); // 默认不缓存

// 显式启用缓存
fetch('https://api.example.com/data', { cache: 'force-cache' });

四层缓存体系

// 1. Request Memoization(请求去重)
// 同一渲染过程中的重复请求会自动去重
async function getUser(id: string) {
  // 即使多个组件调用,也只发起一次请求
  return fetch(`/api/users/${id}`);
}

// 2. Data Cache(数据缓存)
// 跨请求的数据缓存
async function getProducts() {
  return fetch('/api/products', {
    cache: 'force-cache', // 缓存数据
    next: {
      revalidate: 3600, // 1 小时后重新验证
      tags: ['products'], // 缓存标签
    },
  });
}

// 3. Full Route Cache(完整路由缓存)
// 静态路由的 HTML 和 RSC Payload 缓存
// 通过 export const dynamic = 'force-static' 启用

// 4. Router Cache(客户端路由缓存)
// 浏览器端的预取和导航缓存
// 自动管理,30 秒内重访使用缓存

按需重新验证

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

import { revalidatePath, revalidateTag } from 'next/cache';

export async function updateProduct(id: string, data: ProductData) {
  await db.product.update({ where: { id }, data });
  
  // 方式 1:重新验证特定路径
  revalidatePath(`/products/${id}`);
  
  // 方式 2:重新验证路径及其子路径
  revalidatePath('/products', 'layout');
  
  // 方式 3:重新验证特定标签
  revalidateTag('products');
}

// 在 fetch 中使用标签
async function getProduct(id: string) {
  return fetch(`/api/products/${id}`, {
    next: { tags: ['products', `product-${id}`] },
  });
}

TypeScript 增强

改进的类型推断

// Next.js 15 自动推断 params 和 searchParams 类型
// app/products/[category]/[id]/page.tsx
export default function ProductPage({
  params,
  searchParams,
}: {
  params: { category: string; id: string };
  searchParams: { view?: string; sort?: string };
}) {
  // TypeScript 完全理解参数类型
  const { category, id } = params;
  const { view = 'grid', sort = 'popular' } = searchParams;
  
  return <div>...</div>;
}

// generateStaticParams 的类型推断
export async function generateStaticParams() {
  const products = await getProducts();
  
  // 返回类型自动匹配 params
  return products.map((product) => ({
    category: product.category,
    id: product.id,
  }));
}

Server Actions 类型安全

// 使用 Zod 实现端到端类型安全
// app/actions/contact.ts
'use server';

import { z } from 'zod';

const contactSchema = z.object({
  name: z.string().min(2, '姓名至少 2 个字符'),
  email: z.string().email('请输入有效的邮箱'),
  message: z.string().min(10, '消息至少 10 个字符'),
});

type ContactForm = z.infer<typeof contactSchema>;

type ActionResult = 
  | { success: true }
  | { success: false; errors: Record<string, string[]> };

export async function submitContact(formData: FormData): Promise<ActionResult> {
  const result = contactSchema.safeParse({
    name: formData.get('name'),
    email: formData.get('email'),
    message: formData.get('message'),
  });
  
  if (!result.success) {
    return {
      success: false,
      errors: result.error.flatten().fieldErrors,
    };
  }
  
  await sendEmail(result.data);
  
  return { success: true };
}

迁移指南:从 Next.js 14 升级

步骤 1:更新依赖

npm install next@15 react@19 react-dom@19

步骤 2:检查破坏性变更

// 1. 缓存行为变化
// 之前(Next.js 14)
fetch('/api/data'); // 默认缓存

// 之后(Next.js 15)
fetch('/api/data'); // 默认不缓存
fetch('/api/data', { cache: 'force-cache' }); // 显式缓存

// 2. Route Handlers 默认行为
// 之前:GET 请求默认静态
// 之后:GET 请求默认动态

// 如需静态,显式声明
export const dynamic = 'force-static';

export async function GET() {
  // ...
}

// 3. Client Router Cache
// 之前:5 分钟
// 之后:30 秒(提高数据新鲜度)

步骤 3:采用新特性

// 启用新特性
// next.config.ts
const nextConfig: NextConfig = {
  experimental: {
    ppr: true,          // 部分预渲染
    reactCompiler: true, // React Compiler(实验性)
  },
};

总结

Next.js 15 是一个里程碑版本,它将 React Server Components、Turbopack、部分预渲染等技术推向成熟。

关键要点回顾

  • Turbopack 生产就绪:开发速度提升 10 倍
  • RSC 最佳实践:Server/Client 组件合理划分
  • Server Actions:简化表单处理和数据变更
  • PPR:静态外壳 + 动态内容的最佳体验
  • 缓存策略调整:默认不缓存,更可预测

立即行动

  1. 使用 next dev --turbo 体验 Turbopack
  2. 审查现有组件,优化 Server/Client 边界
  3. 将表单处理迁移到 Server Actions
  4. 评估 PPR 对你的应用的适用性

相关资源