Token导航 LogoToken导航TokenDH.com
研究检索需要联网github未标认证来源可访问clear审计通过

nextjs-app-routerNext.js 应用 router

Agent Skill

用于辅助前端页面、组件、样式和交互逻辑的开发与维护。它适合让 Agent 生成或审查 React、Next.js、Vue、Tailwind、CSS 等相关代码,整理组件结构,或定位布局和性能问题。使用时需要结合项目现有设计系统、路由和构建方式,避免只生成孤立片段;涉及页面改动时,应配合本地预览和构建检查确认视觉效果。

总安装

855

周安装

36

GitHub Stars

142

下载量

5
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

3

许可证

MIT

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:nextjs-app-router(Next.js 应用 router)
来源仓库:https://github.com/thebushidocollective/han
仓库路径:skills/nextjs-app-router
安装命令:
npx skills add https://github.com/thebushidocollective/han --skill nextjs-app-router
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。不同来源提供的安装方式可能略有差异;本站展示可直接复制的安装命令,安装前请核对来源页面。

skills.shnpx skills
npx skills add https://github.com/thebushidocollective/han --skill nextjs-app-router

简介

用于辅助前端页面、组件、样式和交互逻辑的开发与维护。

  • 它适合让 Agent 生成或审查 React、Next.js、Vue、Tailwind、CSS 等相关代码,整理组件结构。
  • 使用时需要结合项目现有设计系统、路由和构建方式,避免只生成孤立片段。
  • 涉及页面改动时,应配合本地预览和构建检查确认视觉效果。
  • 通过 npx skills add 命令从指定 GitHub 路径安装并使用。

SKILL.md

Next.js App Router

Master the Next.js App Router for building modern, performant web applications with server components and advanced routing.

App Directory Structure

The app directory uses file-system based routing with special files:

app/
  layout.tsx       # Root layout (required)
  page.tsx         # Home page
  loading.tsx      # Loading UI
  error.tsx        # Error UI
  not-found.tsx    # 404 UI
  template.tsx     # Re-rendered layout
  about/
    page.tsx       # /about
  blog/
    layout.tsx     # Blog-specific layout
    page.tsx       # /blog
    loading.tsx    # Blog loading state
    [slug]/
      page.tsx     # /blog/[slug]
  dashboard/
    (auth)/        # Route group (doesn't affect URL)
      layout.tsx   # Layout for auth routes
      settings/
        page.tsx   # /dashboard/settings
      profile/
        page.tsx   # /dashboard/profile

// app/layout.tsx - Root layout (required)
export default function RootLayout({
  children
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>
        <header>
          <Navigation />
        </header>
        <main>{children}</main>
        <footer>
          <Footer />
        </footer>
      </body>
    </html>
  );
}

Layouts: Root, Nested, and Templates

// app/layout.tsx - Root Layout (wraps entire app)
import { Inter } from 'next/font/google';
import './globals.css';

const inter = Inter({ subsets: ['latin'] });

export const metadata = {
  title: 'My App',
  description: 'Built with Next.js App Router'
};

export default function RootLayout({
  children
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <Providers>
          <Navigation />
          {children}
        </Providers>
      </body>
    </html>
  );
}

// app/dashboard/layout.tsx - Nested Layout
export default function DashboardLayout({
  children
}: {
  children: React.ReactNode
}) {
  return (
    <div className="dashboard">
      <aside>
        <DashboardNav />
      </aside>
      <section>{children}</section>
    </div>
  );
}

// app/dashboard/template.tsx - Template (re-renders on navigation)
// Use when you need fresh state on each navigation
export default function DashboardTemplate({
  children
}: {
  children: React.ReactNode
}) {
  // This re-renders and resets state on navigation
  return <div className="animate-fade-in">{children}</div>;
}

Dynamic Routes and generateStaticParams

// app/blog/[slug]/page.tsx
interface PageProps {
  params: { slug: string };
  searchParams: { [key: string]: string | string[] | undefined };
}

export default async function BlogPost({ params }: PageProps) {
  const post = await getPost(params.slug);

  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  );
}

// Generate static paths at build time (SSG)
export async function generateStaticParams() {
  const posts = await getPosts();

  return posts.map((post) => ({
    slug: post.slug
  }));
}

// Generate metadata dynamically
export async function generateMetadata({ params }: PageProps) {
  const post = await getPost(params.slug);

  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      images: [{ url: post.image }]
    }
  };
}

// Multiple dynamic segments: app/shop/[category]/[product]/page.tsx
export default async function Product({
  params
}: {
  params: { category: string; product: string }
}) {
  const product = await getProduct(params.category, params.product);
  return <div>{product.name}</div>;
}

export async function generateStaticParams() {
  const products = await getProducts();

  return products.map((product) => ({
    category: product.category,
    product: product.slug
  }));
}

// Catch-all routes: app/docs/[...slug]/page.tsx
export default function Docs({ params }: { params: { slug: string[] } }) {
  // /docs/a/b/c -> params.slug = ['a', 'b', 'c']
  const path = params.slug.join('/');
  return <div>Documentation: {path}</div>;
}

// Optional catch-all: app/blog/[[...slug]]/page.tsx
// Matches both /blog and /blog/a/b/c

Loading UI and Streaming

// app/blog/loading.tsx - Automatic loading UI
export default function Loading() {
  return (
    <div className="loading">
      <Skeleton />
      <Skeleton />
      <Skeleton />
    </div>
  );
}

// app/blog/page.tsx - Server component with streaming
import { Suspense } from 'react';

export default function BlogPage() {
  return (
    <div>
      <h1>Blog</h1>

      {/* Stream this component independently */}
      <Suspense fallback={<PostsSkeleton />}>
        <BlogPosts />
      </Suspense>

      {/* Stream this separately */}
      <Suspense fallback={<CommentsSkeleton />}>
        <RecentComments />
      </Suspense>
    </div>
  );
}

// Components can stream as they load
async function BlogPosts() {
  const posts = await getPosts(); // Server-side data fetch

  return (
    <div>
      {posts.map(post => (
        <PostCard key={post.id} post={post} />
      ))}
    </div>
  );
}

async function RecentComments() {
  const comments = await getComments();

  return (
    <div>
      {comments.map(comment => (
        <CommentCard key={comment.id} comment={comment} />
      ))}
    </div>
  );
}

Error Boundaries and Error Handling

// app/blog/error.tsx - Error boundary
'use client'; // Error components must be Client Components

import { useEffect } from 'react';

export default function Error({
  error,
  reset
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  useEffect(() => {
    // Log error to error reporting service
    console.error('Blog error:', error);
  }, [error]);

  return (
    <div className="error-container">
      <h2>Something went wrong!</h2>
      <p>{error.message}</p>
      <button onClick={() => reset()}>Try again</button>
    </div>
  );
}

// app/global-error.tsx - Global error boundary
'use client';

export default function GlobalError({
  error,
  reset
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  return (
    <html>
      <body>
        <h2>Application Error</h2>
        <button onClick={() => reset()}>Try again</button>
      </body>
    </html>
  );
}

// app/blog/not-found.tsx - Custom 404 page
import Link from 'next/link';

export default function NotFound() {
  return (
    <div>
      <h2>Post Not Found</h2>
      <p>Could not find the requested blog post.</p>
      <Link href="/blog">View all posts</Link>
    </div>
  );
}

// Programmatically trigger 404
import { notFound } from 'next/navigation';

export default async function Post({ params }: { params: { id: string } }) {
  const post = await getPost(params.id);

  if (!post) {
    notFound(); // Renders closest not-found.tsx
  }

  return <article>{post.content}</article>;
}

Route Groups for Organization

// Route groups don't affect URL structure
app/
  (marketing)/        # Group routes without affecting URLs
    layout.tsx        # Marketing layout
    page.tsx          # / (homepage)
    about/
      page.tsx        # /about
    contact/
      page.tsx        # /contact
  (shop)/
    layout.tsx        # Shop layout
    products/
      page.tsx        # /products
    cart/
      page.tsx        # /cart
  (auth)/
    layout.tsx        # Auth layout
    login/
      page.tsx        # /login
    register/
      page.tsx        # /register

// app/(marketing)/layout.tsx
export default function MarketingLayout({
  children
}: {
  children: React.ReactNode
}) {
  return (
    <>
      <MarketingHeader />
      {children}
      <MarketingFooter />
    </>
  );
}

// app/(shop)/layout.tsx
export default function ShopLayout({
  children
}: {
  children: React.ReactNode
}) {
  return (
    <>
      <ShopHeader />
      <ShopNav />
      {children}
    </>
  );
}

Parallel Routes

// Use parallel routes to render multiple pages in the same layout
app/
  dashboard/
    @analytics/
      page.tsx
    @team/
      page.tsx
    @user/
      page.tsx
    layout.tsx
    page.tsx

// app/dashboard/layout.tsx
export default function DashboardLayout({
  children,
  analytics,
  team,
  user
}: {
  children: React.ReactNode;
  analytics: React.ReactNode;
  team: React.ReactNode;
  user: React.ReactNode;
}) {
  return (
    <div className="dashboard-grid">
      <div className="main">{children}</div>
      <div className="analytics">{analytics}</div>
      <div className="team">{team}</div>
      <div className="user">{user}</div>
    </div>
  );
}

// Conditional rendering with parallel routes
export default function Layout({ user, admin }: {
  user: React.ReactNode;
  admin: React.ReactNode;
}) {
  const session = await getSession();

  return session.isAdmin ? admin : user;
}

Intercepting Routes

// Intercept routes to show modals or overlays
app/
  feed/
    page.tsx
    @modal/
      (.)photo/
        [id]/
          page.tsx
  photo/
    [id]/
      page.tsx

// app/feed/@modal/(.)photo/[id]/page.tsx
// Intercepts /photo/[id] when navigating from /feed
export default function PhotoModal({ params }: { params: { id: string } }) {
  return (
    <Modal>
      <Photo id={params.id} />
    </Modal>
  );
}

// app/photo/[id]/page.tsx
// Direct navigation to /photo/[id] shows full page
export default function PhotoPage({ params }: { params: { id: string } }) {
  return (
    <div className="photo-page">
      <Photo id={params.id} />
    </div>
  );
}

// Intercepting patterns:
// (.) matches same level
// (..) matches one level up
// (..)(..) matches two levels up
// (...) matches from root

Metadata API for SEO

// app/layout.tsx - Static metadata
import type { Metadata } from 'next';

export const metadata: Metadata = {
  title: {
    default: 'My App',
    template: '%s | My App' // Used by child pages
  },
  description: 'My awesome Next.js app',
  keywords: ['nextjs', 'react', 'typescript'],
  authors: [{ name: 'John Doe' }],
  openGraph: {
    title: 'My App',
    description: 'My awesome Next.js app',
    url: 'https://myapp.com',
    siteName: 'My App',
    images: [
      {
        url: 'https://myapp.com/og.png',
        width: 1200,
        height: 630
      }
    ],
    locale: 'en_US',
    type: 'website'
  },
  twitter: {
    card: 'summary_large_image',
    title: 'My App',
    description: 'My awesome Next.js app',
    images: ['https://myapp.com/twitter.png']
  },
  robots: {
    index: true,
    follow: true,
    googleBot: {
      index: true,
      follow: true,
      'max-video-preview': -1,
      'max-image-preview': 'large',
      'max-snippet': -1
    }
  }
};

// app/blog/[slug]/page.tsx - Dynamic metadata
export async function generateMetadata({
  params
}: {
  params: { slug: string }
}): Promise<Metadata> {
  const post = await getPost(params.slug);

  return {
    title: post.title,
    description: post.excerpt,
    authors: [{ name: post.author }],
    openGraph: {
      title: post.title,
      description: post.excerpt,
      images: [post.image],
      publishedTime: post.publishedAt,
      authors: [post.author]
    }
  };
}

// JSON-LD structured data
export default function Article({ params }: { params: { slug: string } }) {
  const post = getPost(params.slug);

  const jsonLd = {
    '@context': 'https://schema.org',
    '@type': 'Article',
    headline: post.title,
    datePublished: post.publishedAt,
    author: {
      '@type': 'Person',
      name: post.author
    }
  };

  return (
    <>
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
      />
      <article>{post.content}</article>
    </>
  );
}

Route Handlers (API Routes)

// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server';

// GET /api/users
export async function GET(request: NextRequest) {
  const searchParams = request.nextUrl.searchParams;
  const query = searchParams.get('query');

  const users = await getUsers(query);

  return NextResponse.json(users);
}

// POST /api/users
export async function POST(request: NextRequest) {
  const body = await request.json();

  const user = await createUser(body);

  return NextResponse.json(user, { status: 201 });
}

// app/api/users/[id]/route.ts
// GET /api/users/:id
export async function GET(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  const user = await getUser(params.id);

  if (!user) {
    return NextResponse.json({ error: 'User not found' }, { status: 404 });
  }

  return NextResponse.json(user);
}

// DELETE /api/users/:id
export async function DELETE(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  await deleteUser(params.id);

  return new NextResponse(null, { status: 204 });
}

// With middleware
export async function GET(request: NextRequest) {
  const session = await getSession(request);

  if (!session) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }

  const data = await getData(session.userId);

  return NextResponse.json(data);
}

Middleware

// middleware.ts (at root level)
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  // Check authentication
  const token = request.cookies.get('token');

  if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', request.url));
  }

  // Add custom header
  const response = NextResponse.next();
  response.headers.set('x-custom-header', 'value');

  return response;
}

// Configure which routes use middleware
export const config = {
  matcher: [
    '/dashboard/:path*',
    '/api/:path*',
    '/((?!_next/static|_next/image|favicon.ico).*)'
  ]
};

// Advanced middleware with rewrites
export function middleware(request: NextRequest) {
  // A/B testing
  const bucket = Math.random() < 0.5 ? 'a' : 'b';
  request.cookies.set('bucket', bucket);

  if (bucket === 'a') {
    return NextResponse.rewrite(new URL('/experiment-a', request.url));
  }

  return NextResponse.next();
}

When to Use This Skill

Use nextjs-app-router when you need to:

  • Build modern Next.js 13+ applications
  • Implement complex routing with layouts
  • Use server and client components effectively
  • Create loading and error boundaries
  • Optimize performance with streaming
  • Build SEO-friendly applications
  • Implement dynamic and static routes
  • Use parallel and intercepting routes
  • Build scalable Next.js applications
  • Implement advanced routing patterns
  • Create type-safe API routes
  • Optimize metadata for social sharing

Best Practices

  1. Use server components by default - Only mark components with 'use client' when they need interactivity or browser APIs.
  2. Implement proper loading states - Use loading.tsx files and Suspense boundaries for better UX during data fetching.
  3. Create granular error boundaries - Place error.tsx files at appropriate levels to handle errors gracefully.
  4. Leverage static generation - Use generateStaticParams for dynamic routes that can be pre-rendered at build time.
  5. Organize with route groups - Use route groups to organize code without affecting URL structure.
  6. Optimize metadata - Implement both static and dynamic metadata for better SEO and social sharing.
  7. Stream content strategically - Use Suspense to stream independent UI sections as they load.
  8. Keep client-side JavaScript minimal - Maximize server components to reduce bundle size and improve performance.
  9. Use middleware wisely - Apply middleware for authentication, redirects, and request modifications.
  10. Test routing behavior - Verify navigation, loading states, and error handling across different routes.

Common Pitfalls

  1. Using client components unnecessarily - Marking components with 'use client' when they don't need browser APIs increases bundle size.
  2. Not implementing loading states - Missing loading.tsx files lead to poor UX during navigation and data fetching.
  3. Forgetting error boundaries - Without error.tsx files, errors crash the entire application instead of failing gracefully.
  4. Mixing server and client code incorrectly - Importing server-only code in client components or vice versa causes errors.
  5. Not optimizing for static generation - Missing generateStaticParams means pages render on-demand instead of at build time.
  6. Overusing dynamic routes - Too many dynamic segments can make routing complex and hard to maintain.
  7. Not handling route parameters properly - Failing to validate or sanitize route parameters can cause errors or security issues.
  8. Ignoring SEO considerations - Missing or incomplete metadata hurts search engine rankings and social sharing.
  9. Not testing edge cases - Skipping tests for 404s, errors, and loading states leads to poor user experience.
  10. Misunderstanding file conventions - Naming files incorrectly (e.g., using Loading.tsx instead of loading.tsx) breaks conventions.

Resources

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

04

需要参考平台分布和安装热度时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

补充不同宿主或平台的使用分布数据

能力 5

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Codex

29.34%
按下载量换算1

Claude Code

20.94%
按下载量换算1

OpenCode

19.28%
按下载量换算1

Gemini CLI

11.36%
按下载量换算1

Antigravity

8.2%
按下载量换算0

windsurf

3.09%
按下载量换算0

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。

来源信息

继续浏览同类 Skills