mcpField
N
nextjs-server-navigation

nextjs-server-navigation

github.com/wsimmonds开发109304 次安装53 周前更新
代码仓库

关于 nextjs-server-navigation

面向 Next.js 开发者的导航模式技能,说明如何在 Server Component 中使用 Link、redirect() 和 Server Action,并避免为简单导航引入客户端逻辑。

主要功能

  1. 01使用 Link 创建页面链接
  2. 02使用 redirect() 条件跳转
  3. 03通过 Server Action 处理表单导航
  4. 04区分服务端与客户端导航方式

适用场景

  1. 01为服务端页面添加静态链接
  2. 02按认证或权限结果跳转
  3. 03实现表单提交后的页面跳转

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/wsimmonds/claude-nextjs-skills --skill nextjs-server-navigation

安装后在 Claude Code 中输入/nextjs-server-navigation确认已加载。

详细介绍

Next.js:服务端组件导航模式

⚠️ 关键规则

服务端组件使用的导航方法与客户端组件不同!

当需求要求使用服务端渲染导航时,例如链接到其他页面、检查后重定向或演示路由模式,请优先在服务端组件中使用 <Link> 和 redirect()。除非涉及仅限客户端的 API,否则仍应避免使用 'use client'。

模式

场景: 构建一个演示正确导航模式的服务端组件

✅ 正确方案:

// 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>
  );
}

❌ 错误方案:

// 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
  // ...
}

服务端导航方法

方法 1:Link 组件(推荐用于链接)
// 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>
  );
}

要点:

  • ✅ 可在服务端组件中使用(无需 'use client')
  • ✅ 可以是异步函数
  • ✅ 可以获取数据
  • ✅ 无需使用 Hook
方法 2:redirect() 函数(用于条件重定向)
// 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>;
}

适用 redirect() 的情况:

  • 根据服务端数据进行条件重定向
  • 身份验证检查
  • 权限验证
  • 基于数据的路由
方法 3:使用服务端操作的按钮
// 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');
}

完整示例:导航模式

// 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:绝不使用 any 类型

// ❌ 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>) { ... }

服务端与客户端导航对比

特性服务端组件客户端组件
<Link>✅ 是✅ 是
redirect()✅ 是❌ 否
useRouter()❌ 否✅ 是
usePathname()❌ 否✅ 是
async function✅ 是❌ 否
'use client'❌ 否✅ 是

应避免的常见错误

❌ 错误 1:为导航添加 'use client'
// ❌ 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>;
}
// ✅ CORRECT
import Link from 'next/link';

// No 'use client' needed!
export default async function Page() {
  return <Link href="/about">About</Link>;
}
❌ 错误 2:在服务端组件中使用 useRouter()
// ❌ 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
  // ...
}
// ✅ 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>;
}
❌ 错误 3:仅为简单导航就将组件改为客户端组件
// ❌ WRONG - Loses server component benefits!
'use client';

export default function Page() {
  return (
    <div>
      <Link href="/dashboard">Dashboard</Link>
    </div>
  );
}
// ✅ 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>
  );
}

高级模式

在服务端操作中进行编程式导航
// 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}`);
}
服务端组件中的多个链接
// 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>
  );
}

快速决策树

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()

何时改用客户端导航

仅当你需要以下功能时,才使用客户端组件('use client' + useRouter()):

  • 根据客户端状态进行编程式导航
  • 在客户端动画结束后导航
  • 仅限浏览器的 API(window、localStorage)
  • React Hook(useState、useEffect)

其他所有情况都使用服务端组件导航!

快速检查清单

看到“演示导航模式”时:

  • 创建服务端组件(不使用 'use client')
  • 从 'next/link' 导入 Link
  • 添加带有 href 属性的 <Link> 组件
  • 如果要获取数据,请将组件保留为 async
  • 不要从 next/navigation 导入 useRouter
  • 不要添加 'use client' 指令
  • 使用正确的 TypeScript 类型(不使用 any)

总结

服务端组件导航:

  • ✅ 使用 <Link> 创建导航链接
  • ✅ 使用 redirect() 进行条件重定向
  • ✅ 如有需要,将组件保留为异步函数
  • ✅ 无需使用 'use client'
  • ✅ 无需使用 Hook

对于静态链接,这种模式比客户端导航更简单、性能更好!

评论

支持 Markdown