react-nextjs

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Next.js App Router Best Practices

Next.js App Router 最佳实践

Next.js 14+ with Vercel optimization patterns.
适用于Next.js 14+的Vercel优化模式。

Instructions

操作指南

1. App Router Structure

1. App Router 结构

app/
├── layout.tsx          # Root layout
├── page.tsx            # Home page
├── loading.tsx         # Loading UI
├── error.tsx           # Error UI
├── not-found.tsx       # 404 page
├── (auth)/             # Route group
│   ├── login/
│   └── register/
└── api/
    └── users/
        └── route.ts    # API route
app/
├── layout.tsx          # 根布局
├── page.tsx            # 首页
├── loading.tsx         # 加载UI
├── error.tsx           # 错误UI
├── not-found.tsx       # 404页面
├── (auth)/             # 路由组
│   ├── login/
│   └── register/
└── api/
    └── users/
        └── route.ts    # API路由

2. Server vs Client Components

2. Server 组件 vs Client 组件

tsx
// ✅ Server Component (default) - no directive
async function UserList() {
  const users = await getUsers(); // Direct DB access
  return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}

// ✅ Client Component - needs interactivity
'use client';
function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
tsx
// ✅ Server Component(默认)- 无需指令
async function UserList() {
  const users = await getUsers(); // 直接访问数据库
  return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}

// ✅ Client Component - 用于交互场景
'use client';
function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}

3. Prevent Request Waterfall

3. 预防请求瀑布

tsx
// ❌ Bad - sequential (waterfall)
async function Page() {
  const user = await getUser();
  const posts = await getPosts(user.id);
  const comments = await getComments(posts[0].id);
  return ...;
}

// ✅ Good - parallel with Promise.all
async function Page() {
  const [user, posts] = await Promise.all([
    getUser(),
    getPosts()
  ]);
  return ...;
}

// ✅ Good - defer non-critical
async function Page() {
  const user = await getUser();
  const postsPromise = getPosts(user.id); // Don't await yet
  
  return (
    <div>
      <UserProfile user={user} />
      <Suspense fallback={<PostsSkeleton />}>
        <Posts promise={postsPromise} />
      </Suspense>
    </div>
  );
}
tsx
// ❌ 不良示例 - 串行(请求瀑布)
async function Page() {
  const user = await getUser();
  const posts = await getPosts(user.id);
  const comments = await getComments(posts[0].id);
  return ...;
}

// ✅ 良好示例 - 使用Promise.all并行请求
async function Page() {
  const [user, posts] = await Promise.all([
    getUser(),
    getPosts()
  ]);
  return ...;
}

// ✅ 良好示例 - 延迟加载非关键内容
async function Page() {
  const user = await getUser();
  const postsPromise = getPosts(user.id); // 暂不等待结果
  
  return (
    <div>
      <UserProfile user={user} />
      <Suspense fallback={<PostsSkeleton />}>
        <Posts promise={postsPromise} />
      </Suspense>
    </div>
  );
}

4. No Barrel Files (Bundle Optimization)

4. 避免桶文件(打包优化)

tsx
// ❌ Bad - barrel file imports entire module
import { Button, Card } from '@/components';

// ✅ Good - direct imports
import { Button } from '@/components/Button';
import { Card } from '@/components/Card';
tsx
// ❌ 不良示例 - 桶文件会导入整个模块
import { Button, Card } from '@/components';

// ✅ 良好示例 - 直接导入
import { Button } from '@/components/Button';
import { Card } from '@/components/Card';

5. Dynamic Imports

5. 动态导入

tsx
// ✅ Lazy load heavy components
import dynamic from 'next/dynamic';

const HeavyChart = dynamic(() => import('@/components/Chart'), {
  loading: () => <ChartSkeleton />,
  ssr: false
});
tsx
// ✅ 懒加载大型组件
import dynamic from 'next/dynamic';

const HeavyChart = dynamic(() => import('@/components/Chart'), {
  loading: () => <ChartSkeleton />,
  ssr: false
});

6. Server Actions

6. Server Actions

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

import { revalidatePath } from 'next/cache';

export async function createPost(formData: FormData) {
  const title = formData.get('title');
  
  await db.post.create({ data: { title } });
  
  revalidatePath('/posts');
}

// Usage in component
<form action={createPost}>
  <input name="title" />
  <button type="submit">Create</button>
</form>
tsx
// app/actions.ts
'use server';

import { revalidatePath } from 'next/cache';

export async function createPost(formData: FormData) {
  const title = formData.get('title');
  
  await db.post.create({ data: { title } });
  
  revalidatePath('/posts');
}

// 在组件中使用
<form action={createPost}>
  <input name="title" />
  <button type="submit">创建</button>
</form>

7. Caching Strategies

7. 缓存策略

tsx
// ✅ React.cache for request deduplication
import { cache } from 'react';

export const getUser = cache(async (id: string) => {
  return await db.user.findUnique({ where: { id } });
});

// ✅ Next.js fetch cache
const data = await fetch(url, {
  next: { revalidate: 3600 } // Revalidate hourly
});

// ✅ Static generation
export const dynamic = 'force-static';
tsx
// ✅ 使用React.cache实现请求去重
import { cache } from 'react';

export const getUser = cache(async (id: string) => {
  return await db.user.findUnique({ where: { id } });
});

// ✅ Next.js fetch缓存
const data = await fetch(url, {
  next: { revalidate: 3600 } // 每小时重新验证一次
});

// ✅ 静态生成
export const dynamic = 'force-static';

8. Metadata

8. 元数据

tsx
// Static metadata
export const metadata: Metadata = {
  title: 'My App',
  description: 'Description...'
};

// Dynamic metadata
export async function generateMetadata({ params }): Promise<Metadata> {
  const product = await getProduct(params.id);
  return { title: product.name };
}
tsx
// 静态元数据
export const metadata: Metadata = {
  title: 'My App',
  description: 'Description...'
};

// 动态元数据
export async function generateMetadata({ params }): Promise<Metadata> {
  const product = await getProduct(params.id);
  return { title: product.name };
}

References

参考资料