使用 Next.js App Router 的十个常见错误

本文列举了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 />
    </>
  );
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值