本文列举了10个使用AppRouter的常见错误,以下内容来源自Vercel的YouTube:https://youtu.be/RBM03RihZVs?si=LJe-9OU8dhnWqMiF
1. 在服务器组件中调用路由处理器
问题:
许多人喜欢在服务器组件中调用路由处理器 (Route Handler) 来获取数据,这会导致额外的网络请求和复杂性。
错误示例:
// 路由处理器
export async function GET() {
return new Response(JSON.stringify({ message: 'Hello, World!' }));
}
// 服务器组件
export default async function Page() {
const response = await fetch('http://localhost:3000/api');
const data = await response.json();
return <h1>{data.message}</h1>;
}
解决方法:
直接在服务器组件中调用获取数据的逻辑:
async function fetchMessage() {
return { message: 'Hello, World!' };
}
export default async function Page() {
const data = await fetchMessage();
return <h1>{data.message}</h1>;
}
2. 路由处理器的缓存行为
问题:
路由处理器默认是 静态缓存 的,生产环境下数据不会实时更新。如果需要动态数据就会导致问题。
错误示例:
export async function GET() {
return new Response(JSON.stringify({ date: new Date().toLocaleString() }));
}
生产环境中刷新页面,时间不会更新。
解决方法:
通过 Cache-Control 或 Next.js 的 unstable_noStore 来动态化数据:
export async function GET() {
return new Response(JSON.stringify({ date: new Date().toLocaleString() }), {
headers: { 'Cache-Control': 'no-store' },
});
}
3. 客户端组件中错误使用路由处理器
问题:
在客户端组件中通过 fetch 调用路由处理器发送请求,写了很多不必要的代码。
错误示例:
'use client';
export default function Page() {
const handleSubmit = async (e) => {
e.preventDefault();
await fetch('/api', { method: 'POST' });
};
return (
<form onSubmit={handleSubmit}>
<button type="submit">Submit</button>
</form>
);
}
解决方法:
直接使用服务器操作 (Server Actions) 处理逻辑:
// app/actions.js
'use server';
export async function handleFormSubmit() {
console.log('Form submitted on server!');
}
// 客户端组件
'use client';
import { handleFormSubmit } from '../actions';
export default function Page() {
return (
<form action={handleFormSubmit}>
<button type="submit">Submit</button>
</form>
);
}
4. Suspense 边界位置错误
问题:
Suspense 的边界包裹在数据获取组件的下方,无法正确显示回退状态。
错误示例:
import { Suspense } from 'react';
export default async function Page() {
return (
<Suspense fallback={<p>Loading...</p>}>
<BlogList />
</Suspense>
);
}
async function BlogList() {
const blogs = await fetchBlogs();
return blogs.map((blog) => <div key={blog.id}>{blog.title}</div>);
}
解决方法:
将 Suspense 边界移到数据获取组件的上方:
import { Suspense } from 'react';
async function BlogList() {
const blogs = await fetchBlogs();
return blogs.map((blog) => <div key={blog.id}>{blog.title}</div>);
}
export default function Page() {
return (
<Suspense fallback={<p>Loading...</p>}>
<BlogList />
</Suspense>
);
}
5. 未正确处理传入请求信息
问题:
没有充分利用 headers、cookies 或 params 来读取传入的请求数据。
解决方法:
export async function GET(request) {
const headers = request.headers.get('authorization');
const searchParams = new URL(request.url).searchParams;
const name = searchParams.get('name');
return new Response(JSON.stringify({ name, headers }));
}
服务器组件读取 URL 参数示例:
export default function Page({ searchParams }) {
return <h1>Hello, {searchParams.name}!</h1>;
}
6. 上下文提供器位置不当
问题:
上下文提供器 (Context Provider) 放错位置,导致所有子组件被迫变为客户端组件。
解决方法:
将上下文提供器放在根布局中,同时保持其他部分的服务器渲染:
// app/providers.js
'use client';
export function ThemeProvider({ children }) {
return <div className="theme-dark">{children}</div>;
}
// 根布局
import { ThemeProvider } from './providers';
export default function RootLayout({ children }) {
return (
<html>
<body>
<ThemeProvider>{children}</ThemeProvider>
</body>
</html>
);
}
7. 不必要地添加 use client
问题:
在已经位于客户端上下文中的组件中重复添加 use client。
解决方法:
只需在顶层文件添加 use client,子组件会自动继承客户端上下文。
8. 数据修改后未重新验证
问题:
通过表单或 API 修改数据后,没有触发页面的重新验证,导致数据未更新。
解决方法:
使用 revalidatePath 或 revalidateTag 重新验证数据:
'use server';
import { revalidatePath } from 'next/cache';
export async function addTodo() {
await db.todos.insert({ task: 'New Todo' });
revalidatePath('/todos');
}
9. 在 try/catch 中错误使用重定向
问题:
直接在 try/catch 块中调用 redirect,导致逻辑错误。
解决方法:
将 redirect 放在 finally 中,或者在 try/catch 外部调用:
import { redirect } from 'next/navigation';
export async function POST() {
try {
// 执行某些操作
} catch (e) {
console.error(e);
} finally {
redirect('/');
}
}
10. 服务器和客户端组件协作不当
问题:
误以为服务器组件和客户端组件不能一起使用,导致代码重复。
解决方法:
灵活交织服务器和客户端组件。例如:
export default function Page() {
return (
<>
<ServerComponent />
<ClientComponent />
</>
);
}