Next.js 15对App Router做了多项性能优化和API调整,Server Actions从实验特性转为稳定特性,成为前后端数据交互的新范式。相比传统API Route,Server Actions将服务端逻辑直接内联到组件中,省去了手写fetch请求和序列化/反序列化的样板代码。本文从App Router路由架构入手,演示Server Actions在表单提交、数据变更和乐观更新中的完整用法。
App Router路由系统与文件约定
App Router使用app目录下的文件夹层级映射URL路径。每个目录下的特殊文件对应不同功能:page.tsx定义路由页面UI,layout.tsx定义共享布局,loading.tsx定义加载态,error.tsx定义错误边界,not-found.tsx定义404页面。
// 目录结构示例
app/
├── layout.tsx # 根布局(必须)
├── page.tsx # 首页 /
├── blog/
│ ├── layout.tsx # blog区块布局
│ ├── page.tsx # /blog列表页
│ ├── [slug]/
│ │ └── page.tsx # /blog/:slug详情页
│ └── loading.tsx # blog路由加载态
├── dashboard/
│ ├── layout.tsx # dashboard布局(含鉴权)
│ ├── settings/
│ │ └── page.tsx # /dashboard/settings
│ └── error.tsx # dashboard错误边界
动态路由用方括号语法[slug],catch-all路由用[…slug]。Layout组件是嵌套的,父layout包裹子layout,渲染时不重新挂载。page组件切换时重新渲染。
Server Components与Client Components区分
App Router默认所有组件都是Server Components,在服务端渲染,不能使用useState、useEffect等浏览器API。需要客户端交互的组件通过”use client”指令声明。
// app/components/Counter.tsx
"use client";
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
点击次数: {count}
</button>
);
}
Server Components和Client Components的边界划分原则:将交互逻辑和状态管理隔离到叶子节点,尽量缩小”use client”的范围。Server Components可以直接访问数据库和文件系统,渲染后的HTML流式传输给客户端,减少客户端JS包体积。
Server Actions基本用法与表单处理
Server Actions是服务端执行的异步函数,通过”use server”指令声明。前端组件直接调用该函数,框架自动处理网络传输和序列化。
// app/actions/post.ts
"use server";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { db } from "@/lib/db";
export async function createPost(formData: FormData) {
const title = formData.get("title") as string;
const content = formData.get("content") as string;
if (!title || title.trim().length < 2) {
return { error: "标题至少2个字符" };
}
await db.post.create({
data: { title, content, authorId: 1 },
});
revalidatePath("/blog");
redirect("/blog");
}
export async function deletePost(postId: number) {
await db.post.delete({ where: { id: postId } });
revalidatePath("/blog");
revalidatePath(`/blog/${postId}`);
}
在组件中使用Server Actions配合原生form元素:
// app/blog/new/page.tsx
import { createPost } from "@/app/actions/post";
export default function NewPostPage() {
return (
<form action={createPost}>
<input
type="text"
name="title"
placeholder="文章标题"
required
minLength={2}
/>
<textarea
name="content"
placeholder="文章内容"
rows={10}
required
/>
<button type="submit">发布</button>
</form>
);
}
form的action属性直接绑定Server Action,提交时框架自动序列化FormData并发送到服务端执行。无需手写onSubmit和fetch调用。
useFormState与useFormStatus处理表单状态
Next.js 15集成了React的useFormState和useFormStatus Hook,用于追踪表单提交状态和返回结果。这两个Hook只能在Client Components中使用。
// app/components/PostForm.tsx
"use client";
import { useFormState, useFormStatus } from "react-dom";
import { createPost } from "@/app/actions/post";
const initialState = { error: null };
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? "发布中..." : "发布"}
</button>
);
}
export default function PostForm() {
const [state, formAction] = useFormState(createPost, initialState);
return (
<form action={formAction}>
<input type="text" name="title" placeholder="标题" />
<textarea name="content" placeholder="内容" />
{state.error && (
<p className="error">{state.error}</p>
)}
<SubmitButton />
</form>
);
}
useFormState返回状态和包装后的action,状态值由Server Action的返回值更新。useFormStatus提供pending状态,用于禁用按钮和显示加载文案。
缓存与revalidatePath刷新策略
App Router默认对页面做静态缓存(Static Caching)。当数据变更后需要主动刷新缓存,Server Actions中通过revalidatePath或revalidateTag实现。
import { revalidatePath, revalidateTag } from "next/cache";
// 刷新指定路径缓存
revalidatePath("/blog");
revalidatePath(`/blog/${postId}`);
// 刷新指定标签的所有缓存数据
revalidateTag("posts-list");
fetch请求可通过next参数配置缓存策略:
// 强制每次请求都重新获取(动态渲染)
const res = await fetch("https://api.example.com/posts", {
next: { tags: ["posts-list"], revalidate: 3600 },
});
// ISR:每小时重新生成
const res2 = await fetch("https://api.example.com/data", {
next: { revalidate: 3600 },
});
tags与revalidateTag配合使用:当Server Action修改了数据,调用revalidateTag("posts-list")会标记所有使用该tag的fetch缓存为过期,下次请求时自动重新获取。这种精确的缓存失效策略比全页面刷新更高效。
中间件与路由守卫配置
middleware.ts文件放在app目录的同级,用于在请求到达页面之前执行鉴权、重定向等逻辑。
// middleware.ts
import { NextRequest, NextResponse } from "next/server";
import { getToken } from "next-auth/jwt";
export async function middleware(request: NextRequest) {
const token = await getToken({
req: request,
secret: process.env.NEXTAUTH_SECRET,
});
const isLoginPage = request.nextUrl.pathname.startsWith("/login");
if (!token && !isLoginPage) {
return NextResponse.redirect(new URL("/login", request.url));
}
if (token && isLoginPage) {
return NextResponse.redirect(new URL("/dashboard", request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ["/dashboard/:path*", "/login"],
};
matcher字段限定中间件只对指定路径生效,避免对所有请求都执行鉴权逻辑。中间件运行在Edge Runtime上,延迟低但不能使用Node.js API。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/nextjs15approuter-lu-you-jia-gou-yu-serveractions-shu-ju/