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:静态外壳 + 动态内容的最佳体验
- ✅ 缓存策略调整:默认不缓存,更可预测
立即行动
- 使用
next dev --turbo体验 Turbopack - 审查现有组件,优化 Server/Client 边界
- 将表单处理迁移到 Server Actions
- 评估 PPR 对你的应用的适用性


