nextjs-server-navigation

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Next.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
<Link>
and
redirect()
within Server Components. You still avoid
'use client'
unless a client-only API is involved.
Server Components 使用的导航方式与 Client Components 不同!
当需求涉及服务端渲染导航时——例如,链接到其他页面、验证后重定向或展示路由模式——优先在Server Components中使用
<Link>
redirect()
。除非需要使用客户端专属API,否则无需添加
'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

TypeScript:切勿使用
any
类型

typescript
// ❌ 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

服务端与客户端导航对比

FeatureServer ComponentClient Component
<Link>
✅ Yes✅ Yes
redirect()
✅ Yes❌ No
useRouter()
❌ No✅ Yes
usePathname()
❌ No✅ Yes
async function✅ Yes❌ No
'use client'❌ No✅ Yes
特性Server ComponentClient Component
<Link>
✅ 支持✅ 支持
redirect()
✅ 支持❌ 不支持
useRouter()
❌ 不支持✅ 支持
usePathname()
❌ 不支持✅ 支持
异步函数✅ 支持❌ 不支持
'use client'
❌ 不需要✅ 需要

Common Mistakes to Avoid

需避免的常见错误

❌ Mistake 1: Adding 'use client' for Navigation

❌ 错误1:为导航添加
'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 (
'use client'
+
useRouter()
) ONLY when you need:
  • 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
    Link
    from 'next/link'
  • Add
    <Link>
    components with href prop
  • Keep component as
    async
    if fetching data
  • 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
    <Link>
    for navigation links
  • ✅ Use
    redirect()
    for conditional redirects
  • ✅ 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'
  • ✅ 无需使用钩子
对于静态链接,这种模式比客户端导航更简单、性能更优!