nextjs-server-navigation
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseNext.js: Server Component Navigation Pattern
Next.js:服务端组件导航模式
⚠️ CRITICAL RULE
⚠️ 重要规则
Server Components use DIFFERENT navigation methods than Client Components!
When requirements call for server-rendered navigation—for example, linking to other pages, redirecting after a check, or demonstrating routing patterns—prefer and within Server Components. You still avoid unless a client-only API is involved.
<Link>redirect()'use client'Server Components 使用的导航方式与 Client Components 不同!
当需求涉及服务端渲染导航时——例如,链接到其他页面、验证后重定向或展示路由模式——优先在Server Components中使用和。除非需要使用客户端专属API,否则无需添加。
<Link>redirect()'use client'The Pattern
实现模式
Scenario: build a server component that demonstrates proper navigation patterns
✅ CORRECT Solution:
typescript
// app/page.tsx (Server Component - NO 'use client'!)
import Link from 'next/link';
export default async function Page() {
return (
<div>
<h1>Home</h1>
<Link href="/dashboard">Go to Dashboard</Link>
<Link href="/profile">View Profile</Link>
</div>
);
}❌ WRONG Solution:
typescript
// app/page.tsx
'use client'; // ❌ NO! Server components don't need this for navigation!
import { useRouter } from 'next/navigation'; // ❌ Wrong for server components
export default function Page() {
const router = useRouter(); // ❌ This is client-side navigation
// ...
}场景: 构建一个展示正确导航模式的服务端组件
✅ 正确方案:
typescript
// app/page.tsx (Server Component - 无需 'use client'!)
import Link from 'next/link';
export default async function Page() {
return (
<div>
<h1>首页</h1>
<Link href="/dashboard">前往控制台</Link>
<Link href="/profile">查看个人资料</Link>
</div>
);
}❌ 错误方案:
typescript
// app/page.tsx
'use client'; // ❌ 错误!服务端组件导航不需要这个!
import { useRouter } from 'next/navigation'; // ❌ 服务端组件不适用
export default function Page() {
const router = useRouter(); // ❌ 这是客户端导航
// ...
}Server Navigation Methods
服务端导航方式
Method 1: Link Component (Recommended for Links)
方式1:Link组件(推荐用于链接)
typescript
// app/page.tsx
import Link from 'next/link';
export default async function Page() {
// Can still fetch data - this is a server component!
const data = await fetchData();
return (
<div>
<h1>Welcome</h1>
{/* Simple navigation link */}
<Link href="/about">About Us</Link>
{/* Dynamic link */}
<Link href={`/products/${data.productId}`}>View Product</Link>
{/* Link with styling */}
<Link href="/dashboard" className="btn-primary">
Dashboard
</Link>
</div>
);
}Key Points:
- ✅ Works in Server Components (no 'use client' needed)
- ✅ Can be async function
- ✅ Can fetch data
- ✅ No hooks required
typescript
// app/page.tsx
import Link from 'next/link';
export default async function Page() {
// 仍可获取数据 - 这是服务端组件!
const data = await fetchData();
return (
<div>
<h1>欢迎</h1>
{/* 简单导航链接 */}
<Link href="/about">关于我们</Link>
{/* 动态链接 */}
<Link href={`/products/${data.productId}`}>查看产品</Link>
{/* 带样式的链接 */}
<Link href="/dashboard" className="btn-primary">
控制台
</Link>
</div>
);
}关键点:
- ✅ 可在Server Components中使用(无需)
'use client' - ✅ 可以是异步函数
- ✅ 可获取数据
- ✅ 无需使用钩子
Method 2: redirect() Function (For Conditional Redirects)
方式2:redirect()函数(用于条件重定向)
typescript
// app/profile/page.tsx
import { redirect } from 'next/navigation';
import { cookies } from 'next/headers';
export default async function ProfilePage() {
// Check authentication
const cookieStore = await cookies();
const session = cookieStore.get('session');
// Redirect if not authenticated
if (!session) {
redirect('/login');
}
// Fetch user data
const user = await fetchUser(session.value);
return <div>Welcome, {user.name}!</div>;
}When to use :
redirect()- Conditional redirects based on server-side data
- Authentication checks
- Permission validation
- Data-based routing
typescript
// app/profile/page.tsx
import { redirect } from 'next/navigation';
import { cookies } from 'next/headers';
export default async function ProfilePage() {
// 验证身份
const cookieStore = await cookies();
const session = cookieStore.get('session');
// 未登录则重定向
if (!session) {
redirect('/login');
}
// 获取用户数据
const user = await fetchUser(session.value);
return <div>欢迎回来,{user.name}!</div>;
}何时使用:
redirect()- 基于服务端数据的条件重定向
- 身份验证检查
- 权限验证
- 基于数据的路由
Method 3: Button with Server Action
方式3:结合Server Action的按钮
typescript
// app/page.tsx
import { logout } from './actions';
export default async function Page() {
return (
<div>
<h1>Dashboard</h1>
<form action={logout}>
<button type="submit">Logout</button>
</form>
</div>
);
}
// app/actions.ts
'use server';
import { redirect } from 'next/navigation';
export async function logout() {
// Clear session
await clearSession();
// Redirect to login page
redirect('/login');
}typescript
// app/page.tsx
import { logout } from './actions';
export default async function Page() {
return (
<div>
<h1>控制台</h1>
<form action={logout}>
<button type="submit">退出登录</button>
</form>
</div>
);
}
// app/actions.ts
'use server';
import { redirect } from 'next/navigation';
export async function logout() {
// 清除会话
await clearSession();
// 重定向到登录页
redirect('/login');
}Complete Example: Navigation Patterns
完整示例:导航模式
typescript
// app/page.tsx - Demonstrates multiple navigation patterns
import Link from 'next/link';
import { redirect } from 'next/navigation';
import { headers } from 'next/headers';
export default async function HomePage() {
// Server-side logic
const headersList = await headers();
const userAgent = headersList.get('user-agent');
// Conditional redirect example
if (userAgent?.includes('bot')) {
redirect('/bot-page');
}
return (
<div>
<h1>Welcome to Our App</h1>
{/* Navigation Links */}
<nav>
<Link href="/about">About</Link>
<Link href="/products">Products</Link>
<Link href="/contact">Contact</Link>
</nav>
{/* Button-style link */}
<Link href="/get-started" className="button">
Get Started
</Link>
{/* Dynamic link */}
<Link href={`/user/${123}`}>View Profile</Link>
</div>
);
}typescript
// app/page.tsx - 展示多种导航模式
import Link from 'next/link';
import { redirect } from 'next/navigation';
import { headers } from 'next/headers';
export default async function HomePage() {
// 服务端逻辑
const headersList = await headers();
const userAgent = headersList.get('user-agent');
// 条件重定向示例
if (userAgent?.includes('bot')) {
redirect('/bot-page');
}
return (
<div>
<h1>欢迎使用我们的应用</h1>
{/* 导航链接 */}
<nav>
<Link href="/about">关于我们</Link>
<Link href="/products">产品</Link>
<Link href="/contact">联系我们</Link>
</nav>
{/* 按钮样式链接 */}
<Link href="/get-started" className="button">
开始使用
</Link>
{/* 动态链接 */}
<Link href={`/user/${123}`}>查看个人资料</Link>
</div>
);
}TypeScript: NEVER Use any
Type
anyTypeScript:切勿使用any
类型
anytypescript
// ❌ WRONG
function handleClick(e: any) { ... }
// ✅ CORRECT - Not needed in server components!
// Server components don't have onClick handlers
// For client components with handlers:
'use client';
function handleClick(e: React.MouseEvent<HTMLButtonElement>) { ... }typescript
// ❌ 错误
function handleClick(e: any) { ... }
// ✅ 正确 - 服务端组件中不需要!
// 服务端组件没有onClick事件处理器
// 对于带有处理器的客户端组件:
'use client';
function handleClick(e: React.MouseEvent<HTMLButtonElement>) { ... }Server vs Client Navigation Comparison
服务端与客户端导航对比
| Feature | Server Component | Client Component |
|---|---|---|
| ✅ Yes | ✅ Yes |
| ✅ Yes | ❌ No |
| ❌ No | ✅ Yes |
| ❌ No | ✅ Yes |
| async function | ✅ Yes | ❌ No |
| 'use client' | ❌ No | ✅ Yes |
| 特性 | Server Component | Client Component |
|---|---|---|
| ✅ 支持 | ✅ 支持 |
| ✅ 支持 | ❌ 不支持 |
| ❌ 不支持 | ✅ 支持 |
| ❌ 不支持 | ✅ 支持 |
| 异步函数 | ✅ 支持 | ❌ 不支持 |
| ❌ 不需要 | ✅ 需要 |
Common Mistakes to Avoid
需避免的常见错误
❌ Mistake 1: Adding 'use client' for Navigation
❌ 错误1:为导航添加'use client'
'use client'typescript
// ❌ WRONG
'use client'; // Don't add this just for navigation!
import Link from 'next/link';
export default function Page() {
return <Link href="/about">About</Link>;
}typescript
// ✅ CORRECT
import Link from 'next/link';
// No 'use client' needed!
export default async function Page() {
return <Link href="/about">About</Link>;
}typescript
// ❌ 错误
'use client'; // 不要只为了导航添加这个!
import Link from 'next/link';
export default function Page() {
return <Link href="/about">关于我们</Link>;
}typescript
// ✅ 正确
import Link from 'next/link';
// 不需要`'use client'`!
export default async function Page() {
return <Link href="/about">关于我们</Link>;
}❌ Mistake 2: Using useRouter() in Server Component
❌ 错误2:在Server Component中使用useRouter()
typescript
// ❌ WRONG
import { useRouter } from 'next/navigation'; // This is for CLIENT components!
export default async function Page() {
const router = useRouter(); // ERROR! Can't use hooks in server components
// ...
}typescript
// ✅ CORRECT - Use Link or redirect()
import Link from 'next/link';
import { redirect } from 'next/navigation';
export default async function Page() {
// Conditional redirect
const shouldRedirect = await checkSomething();
if (shouldRedirect) {
redirect('/other-page');
}
// Or navigation links
return <Link href="/other-page">Go</Link>;
}typescript
// ❌ 错误
import { useRouter } from 'next/navigation'; // 这是给客户端组件用的!
export default async function Page() {
const router = useRouter(); // 错误!服务端组件不能使用钩子
// ...
}typescript
// ✅ 正确 - 使用Link或redirect()
import Link from 'next/link';
import { redirect } from 'next/navigation';
export default async function Page() {
// 条件重定向
const shouldRedirect = await checkSomething();
if (shouldRedirect) {
redirect('/other-page');
}
// 或使用导航链接
return <Link href="/other-page">前往</Link>;
}❌ Mistake 3: Making Component Client-Side for Simple Navigation
❌ 错误3:为简单导航将组件设为客户端组件
typescript
// ❌ WRONG - Loses server component benefits!
'use client';
export default function Page() {
return (
<div>
<Link href="/dashboard">Dashboard</Link>
</div>
);
}typescript
// ✅ CORRECT - Keep it as a server component!
export default async function Page() {
// Can now fetch data server-side
const data = await fetchData();
return (
<div>
<Link href="/dashboard">Dashboard</Link>
<p>{data.message}</p>
</div>
);
}typescript
// ❌ 错误 - 失去了服务端组件的优势!
'use client';
export default function Page() {
return (
<div>
<Link href="/dashboard">控制台</Link>
</div>
);
}typescript
// ✅ 正确 - 保持为服务端组件!
export default async function Page() {
// 现在可以在服务端获取数据
const data = await fetchData();
return (
<div>
<Link href="/dashboard">控制台</Link>
<p>{data.message}</p>
</div>
);
}Advanced Patterns
进阶模式
Programmatic Navigation in Server Actions
Server Actions中的编程式导航
typescript
// app/page.tsx
import { createPost } from './actions';
export default async function Page() {
return (
<form action={createPost}>
<input name="title" required />
<button type="submit">Create Post</button>
</form>
);
}
// app/actions.ts
'use server';
import { redirect } from 'next/navigation';
export async function createPost(formData: FormData) {
const title = formData.get('title') as string;
// Save to database
const post = await db.posts.create({ title });
// Redirect to the new post
redirect(`/posts/${post.id}`);
}typescript
// app/page.tsx
import { createPost } from './actions';
export default async function Page() {
return (
<form action={createPost}>
<input name="title" required />
<button type="submit">创建文章</button>
</form>
);
}
// app/actions.ts
'use server';
import { redirect } from 'next/navigation';
export async function createPost(formData: FormData) {
const title = formData.get('title') as string;
// 保存到数据库
const post = await db.posts.create({ title });
// 重定向到新文章页面
redirect(`/posts/${post.id}`);
}Multiple Links in Server Component
Server Component中的多链接示例
typescript
// app/page.tsx
import Link from 'next/link';
export default async function NavigationPage() {
const pages = await fetchPages();
return (
<nav>
<h2>Site Navigation</h2>
<ul>
{pages.map((page) => (
<li key={page.id}>
<Link href={`/pages/${page.slug}`}>
{page.title}
</Link>
</li>
))}
</ul>
</nav>
);
}typescript
// app/page.tsx
import Link from 'next/link';
export default async function NavigationPage() {
const pages = await fetchPages();
return (
<nav>
<h2>站点导航</h2>
<ul>
{pages.map((page) => (
<li key={page.id}>
<Link href={`/pages/${page.slug}`}>
{page.title}
</Link>
</li>
))}
</ul>
</nav>
);
}Quick Decision Tree
快速决策树
Need navigation in a component?
│
├─ Is it a Server Component (no 'use client')?
│ ├─ Static link → Use <Link>
│ ├─ Conditional redirect → Use redirect()
│ └─ Form submission → Server Action with redirect()
│
└─ Is it a Client Component ('use client')?
├─ Link → Use <Link> (works in both!)
└─ Programmatic → Use useRouter()需要在组件中实现导航?
│
├─ 是否为Server Component(无`'use client'`)?
│ ├─ 静态链接 → 使用<Link>
│ ├─ 条件重定向 → 使用redirect()
│ └─ 表单提交 → 结合redirect()的Server Action
│
└─ 是否为Client Component(有`'use client'`)?
├─ 链接 → 使用<Link>(两者都支持!)
└─ 编程式导航 → 使用useRouter()When to Use Client-Side Navigation Instead
何时改用客户端导航
Use Client Components ( + ) ONLY when you need:
'use client'useRouter()- Programmatic navigation based on client state
- Navigation after client-side animations
- Browser-only APIs (window, localStorage)
- React hooks (useState, useEffect)
For everything else, use Server Component navigation!
仅在需要以下功能时,才使用Client Components( + ):
'use client'useRouter()- 基于客户端状态的编程式导航
- 客户端动画完成后的导航
- 浏览器专属API(window、localStorage)
- React钩子(useState、useEffect)
其他所有场景,都使用服务端组件导航!
Quick Checklist
快速检查清单
When you see "demonstrates navigation patterns":
- Create a server component (no 'use client')
- Import from 'next/link'
Link - Add components with href prop
<Link> - Keep component as if fetching data
async - Do NOT import useRouter from next/navigation
- Do NOT add 'use client' directive
- Use proper TypeScript types (no )
any
当你需要「展示导航模式」时:
- 创建服务端组件(无)
'use client' - 从'next/link'导入
Link - 添加带有href属性的组件
<Link> - 如果需要获取数据,保持组件为
async - 不要从next/navigation导入useRouter
- 不要添加指令
'use client' - 使用正确的TypeScript类型(不要用)
any
Summary
总结
Server Component Navigation:
- ✅ Use for navigation links
<Link> - ✅ Use for conditional redirects
redirect() - ✅ Keep component async if needed
- ✅ No 'use client' required
- ✅ No hooks needed
This pattern is simpler and more performant than client-side navigation for static links!
Server Component导航:
- ✅ 使用实现导航链接
<Link> - ✅ 使用实现条件重定向
redirect() - ✅ 必要时保持组件为异步
- ✅ 无需
'use client' - ✅ 无需使用钩子
对于静态链接,这种模式比客户端导航更简单、性能更优!