Next.js应用路由器技能Skill nextjs-app-router

这个技能用于使用Next.js 13+的App Router架构构建现代Web应用,支持服务器组件、客户端组件、高级路由模式,优化数据获取和渲染性能,提升SEO友好性。关键词:Next.js, App Router, 服务器渲染, React, 前端开发

前端开发 0 次安装 0 次浏览 更新于 3/18/2026

name: Next.js应用路由器技能 description: 使用App Router架构构建现代Next.js 13+应用,包括服务器组件、客户端组件和高级路由模式。用于实现服务器优先渲染、创建嵌套布局、构建并行路由、实现拦截路由、使用React服务器组件、通过服务器端异步组件优化数据获取、使用Suspense实现流式传输、管理客户端交互边界,或利用Next.js 13+应用目录功能来构建高性能、SEO友好的React应用。

Next.js App Router - 现代React框架模式

何时使用此技能

  • 构建使用App Router的Next.js 13+应用
  • 实现服务器组件以进行服务器优先渲染
  • 创建客户端组件用于交互式UI元素
  • 设置跨路由持久化的嵌套布局
  • 实现复杂UI模式的并行路由
  • 使用拦截路由处理模态框和覆盖层
  • 通过异步服务器组件优化数据获取
  • 使用Suspense实现流式传输和渐进渲染
  • 有效管理客户端/服务器组件边界
  • 构建具有服务器端渲染的SEO友好应用
  • 利用服务器动作处理表单
  • 使用应用目录结构创建基于文件的路由

何时使用此技能

  • 构建Next.js 13+应用使用App Router、实现服务器组件或优化React服务器组件模式。
  • 在处理相关任务或功能时
  • 在需要此专业知识的开发期间

使用时机:构建Next.js 13+应用使用App Router、实现服务器组件或优化React服务器组件模式。

核心概念

  1. 默认服务器组件 - 组件默认在服务器渲染,除非标记为’use client’
  2. 基于文件的路由 - /app 目录结构定义路由
  3. 流式传输与Suspense - 渐进渲染以改善用户体验
  4. 数据获取 - 服务器端自动缓存
  5. 布局与模板 - 跨路由共享的UI

App Router 结构

app/
├── layout.tsx          # 根布局(必需)
├── page.tsx            # 主页(/)
├── loading.tsx         # 加载UI
├── error.tsx           # 错误UI
├── not-found.tsx       # 404 UI
├── dashboard/
│   ├── layout.tsx      # 仪表板布局
│   ├── page.tsx        # /dashboard
│   └── settings/
│       └── page.tsx    # /dashboard/settings
└── api/
    └── users/
        └── route.ts    # API端点 /api/users

服务器与客户端组件

// ✅ 服务器组件(默认 - 无'use client')
// app/page.tsx
import { db } from '@/lib/db';

export default async function Page() {
  // 在服务器获取数据
  const users = await db.user.findMany();
  
  return (
    <div>
      <h1>用户</h1>
      {users.map(user => <UserCard key={user.id} user={user} />)}
    </div>
  );
}

// ✅ 客户端组件(交互功能)
// app/components/Counter.tsx
'use client';

import { useState } from 'react';

export function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <button onClick={() => setCount(count + 1)}>
      计数: {count}
    </button>
  );
}

// ✅ 组合:服务器组件与客户端组件子项
// app/page.tsx
import { Counter } from './components/Counter';

export default async function Page() {
  const data = await fetchData(); // 服务器端
  
  return (
    <div>
      <h1>服务器数据: {data}</h1>
      <Counter /> {/* 客户端交互组件 */}
    </div>
  );
}

数据获取

// ✅ 获取并缓存(默认:'force-cache')
async function getUsers() {
  const res = await fetch('https://api.example.com/users', {
    cache: 'force-cache' // 缓存直到重新验证
  });
  return res.json();
}

// ✅ 每60秒重新验证
async function getUsers() {
  const res = await fetch('https://api.example.com/users', {
    next: { revalidate: 60 }
  });
  return res.json();
}

// ✅ 无缓存(始终新鲜)
async function getUsers() {
  const res = await fetch('https://api.example.com/users', {
    cache: 'no-store'
  });
  return res.json();
}

// ✅ 数据库查询(Prisma)
import { db } from '@/lib/db';

async function getUser(id: string) {
  return await db.user.findUnique({ where: { id } });
}

动态路由

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

export default async function PostPage({ params }: PageProps) {
  const post = await db.post.findUnique({ where: { id: params.id } });
  
  return <div>{post.title}</div>;
}

// 在构建时生成静态路径
export async function generateStaticParams() {
  const posts = await db.post.findMany();
  return posts.map((post) => ({ id: post.id }));
}

// 生成元数据
export async function generateMetadata({ params }: PageProps) {
  const post = await db.post.findUnique({ where: { id: params.id } });
  
  return {
    title: post.title,
    description: post.excerpt
  };
}

布局

// app/layout.tsx(根布局 - 必需)
export default function RootLayout({
  children
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <Header />
        <main>{children}</main>
        <Footer />
      </body>
    </html>
  );
}

// app/dashboard/layout.tsx(嵌套布局)
export default function DashboardLayout({
  children
}: {
  children: React.ReactNode;
}) {
  return (
    <div className="dashboard">
      <Sidebar />
      <div className="content">{children}</div>
    </div>
  );
}

加载与错误状态

// app/dashboard/loading.tsx
export default function Loading() {
  return <Spinner />;
}

// app/dashboard/error.tsx
'use client';

export default function Error({
  error,
  reset
}: {
  error: Error;
  reset: () => void;
}) {
  return (
    <div>
      <h2>出错了!</h2>
      <button onClick={reset}>重试</button>
    </div>
  );
}

使用Suspense的流式传输

import { Suspense } from 'react';

export default function Page() {
  return (
    <div>
      <h1>仪表板</h1>
      
      {/* 这个立即加载 */}
      <QuickStats />
      
      {/* 这个在准备好时流式传输 */}
      <Suspense fallback={<Skeleton />}>
        <SlowComponent />
      </Suspense>
    </div>
  );
}

async function SlowComponent() {
  const data = await slowDatabaseQuery();
  return <div>{data}</div>;
}

路由处理程序(API路由)

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

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const page = searchParams.get('page') || '1';
  
  const users = await db.user.findMany({
    skip: (parseInt(page) - 1) * 20,
    take: 20
  });
  
  return NextResponse.json({ users });
}

export async function POST(request: Request) {
  const body = await request.json();
  
  const user = await db.user.create({
    data: body
  });
  
  return NextResponse.json(user, { status: 201 });
}

// 动态路由: app/api/users/[id]/route.ts
export async function GET(
  request: Request,
  { params }: { params: { id: string } }
) {
  const user = await db.user.findUnique({ where: { id: params.id } });
  
  if (!user) {
    return NextResponse.json({ error: '未找到' }, { status: 404 });
  }
  
  return NextResponse.json(user);
}

最佳实践

  • 最小化客户端组件 - 仅在需要时使用’use client’(交互性、钩子)
  • 在服务器获取数据 - 减少客户端捆绑包,提高安全性
  • 流式传输 - 使用Suspense进行渐进渲染
  • 元数据 - 为每个路由生成SEO友好的元数据
  • 并行路由 - 使用@folder约定进行并行渲染
  • 路由组 - 使用(folder)进行组织而不影响URL

资源


记住:App Router是服务器优先的。拥抱服务器组件,谨慎使用客户端组件,并利用流式传输改善用户体验。