nextjs-server-navigation by wsimmonds/claude-nextjs-skills
npx skills add https://github.com/wsimmonds/claude-nextjs-skills --skill nextjs-server-navigation服务端组件使用与客户端组件不同的导航方法!
当需求要求服务端渲染导航时——例如,链接到其他页面、检查后重定向或演示路由模式——在服务端组件中优先使用 <Link> 和 redirect()。除非涉及仅限客户端的 API,否则仍然避免使用 'use client'。
场景: 构建一个演示正确导航模式的服务端组件
✅ 正确解决方案:
// app/page.tsx (服务端组件 - 无 '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>
);
}
❌ 错误解决方案:
// app/page.tsx
'use client'; // ❌ 不!服务端组件不需要这个来进行导航!
import { useRouter } from 'next/navigation'; // ❌ 对服务端组件错误
export default function Page() {
const router = useRouter(); // ❌ 这是客户端导航
// ...
}
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
// 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>
);
}
关键点:
// 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():
// 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');
}
// 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>
);
}
any 类型// ❌ 错误
function handleClick(e: any) { ... }
// ✅ 正确 - 在服务端组件中不需要!
// 服务端组件没有 onClick 处理程序
// 对于带有处理程序的客户端组件:
'use client';
function handleClick(e: React.MouseEvent<HTMLButtonElement>) { ... }
| 功能 | 服务端组件 | 客户端组件 |
|---|---|---|
<Link> | ✅ 是 | ✅ 是 |
redirect() | ✅ 是 | ❌ 否 |
useRouter() | ❌ 否 | ✅ 是 |
usePathname() | ❌ 否 | ✅ 是 |
| 异步函数 | ✅ 是 | ❌ 否 |
| 'use client' | ❌ 否 | ✅ 是 |
// ❌ 错误
'use client'; // 不要仅为导航添加这个!
import Link from 'next/link';
export default function Page() {
return <Link href="/about">关于</Link>;
}
// ✅ 正确
import Link from 'next/link';
// 不需要 'use client'!
export default async function Page() {
return <Link href="/about">关于</Link>;
}
// ❌ 错误
import { useRouter } from 'next/navigation'; // 这是用于客户端组件的!
export default async function Page() {
const router = useRouter(); // 错误!不能在服务端组件中使用钩子
// ...
}
// ✅ 正确 - 使用 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>;
}
// ❌ 错误 - 失去服务端组件优势!
'use client';
export default function Page() {
return (
<div>
<Link href="/dashboard">仪表板</Link>
</div>
);
}
// ✅ 正确 - 保持为服务端组件!
export default async function Page() {
// 现在可以在服务端获取数据
const data = await fetchData();
return (
<div>
<Link href="/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">创建文章</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}`);
}
// 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>
);
}
需要在组件中导航吗?
│
├─ 是服务端组件吗(无 'use client')?
│ ├─ 静态链接 → 使用 <Link>
│ ├─ 条件重定向 → 使用 redirect()
│ └─ 表单提交 → 带有 redirect() 的服务端操作
│
└─ 是客户端组件吗('use client')?
├─ 链接 → 使用 <Link>(两者都适用!)
└─ 程序化 → 使用 useRouter()
仅当需要以下情况时使用客户端组件('use client' + useRouter()):
对于其他所有情况,使用服务端组件导航!
当看到"演示导航模式"时:
Link<Link> 组件asyncany)服务端组件导航:
<Link> 进行导航链接redirect() 进行条件重定向对于静态链接,这种模式比客户端导航更简单且性能更好!
每周安装量
118
代码仓库
GitHub 星标
80
首次出现
2026年1月23日
安全审计
安装于
claude-code96
opencode95
codex93
gemini-cli90
github-copilot90
cursor90
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.
Scenario: build a server component that demonstrates proper navigation patterns
✅ CORRECT Solution:
// 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:
// 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
// ...
}
// 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:
// 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 useredirect():
// 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>
);
}
any Type// ❌ 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>) { ... }
| Feature | Server Component | Client Component |
|---|---|---|
<Link> | ✅ Yes | ✅ Yes |
redirect() | ✅ Yes | ❌ No |
useRouter() | ❌ No | ✅ Yes |
usePathname() | ❌ No | ✅ Yes |
| async function | ✅ Yes | ❌ No |
| 'use client' | ❌ No | ✅ Yes |
// ❌ 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>;
}
// ❌ 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>;
}
// ❌ 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 Components ('use client' + useRouter()) ONLY when you need:
For everything else, use Server Component navigation!
When you see "demonstrates navigation patterns":
Link from 'next/link'<Link> components with href propasync if fetching dataany)Server Component Navigation:
<Link> for navigation linksredirect() for conditional redirectsThis pattern is simpler and more performant than client-side navigation for static links!
Weekly Installs
118
Repository
GitHub Stars
80
First Seen
Jan 23, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
claude-code96
opencode95
codex93
gemini-cli90
github-copilot90
cursor90
Vue 3 调试指南:解决响应式、计算属性与监听器常见错误
11,800 周安装