Next.js前端框架Skill nextjs

Next.js 是一个基于 React 的前端开发框架,专为构建生产级全栈 Web 应用设计,支持服务器端渲染、静态生成和自动优化功能。此技能适用于构建高效、可扩展的 Web 应用,实现现代路由架构、数据获取、性能优化和 SEO 提升。关键词:Next.js, React框架, 服务器端渲染, 静态生成, Web开发, 前端框架, 性能优化, SEO, 全栈应用。

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

名称: nextjs 描述: 实现Next.js的指南 - 一个用于生产的React框架,具有服务器端渲染、静态生成和现代Web功能。在构建Next.js应用、实现App Router、处理服务器组件、数据获取、路由或优化性能时使用。 许可证: MIT 版本: 1.0.0

Next.js 技能

Next.js 是一个React框架,用于构建具有服务器端渲染、静态生成和内置强大优化功能的全栈Web应用。

参考

https://nextjs.org/docs/llms.txt

何时使用此技能

在以下情况使用此技能:

  • 构建新的Next.js应用(v15+)
  • 实现App Router架构
  • 处理服务器组件和客户端组件
  • 设置路由、布局和导航
  • 实现数据获取模式
  • 优化图像、字体和性能
  • 配置元数据和SEO
  • 设置API路由和路由处理程序
  • 从Pages Router迁移到App Router
  • 部署Next.js应用

核心概念

App Router vs Pages Router

App Router(推荐用于v13+):

  • 现代架构,具有React服务器组件
  • 基于文件系统的路由在app/目录中
  • 布局、加载状态和错误边界
  • 流式和Suspense支持
  • 具有布局的嵌套路由

Pages Router(传统):

  • 传统页面路由在pages/目录中
  • 使用getStaticPropsgetServerSidePropsgetInitialProps
  • 仍支持现有项目

关键架构原则

  1. 默认服务器组件app/中的组件是服务器组件,除非标记为'use client'
  2. 基于文件的路由:文件系统定义应用路由
  3. 嵌套布局:通过布局跨路由共享UI
  4. 渐进增强:尽可能在没有JavaScript的情况下工作
  5. 自动优化:图像、字体、脚本自动优化

安装与设置

创建新项目

npx create-next-app@latest my-app
# 或
yarn create next-app my-app
# 或
pnpm create next-app my-app
# 或
bun create next-app my-app

交互式设置提示:

  • TypeScript?(推荐是)
  • ESLint?(推荐是)
  • Tailwind CSS?(可选)
  • src/目录?(可选)
  • App Router?(新项目推荐是)
  • 导入别名?(默认:@/*)

手动设置

npm install next@latest react@latest react-dom@latest

package.json脚本:

{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  }
}

项目结构

my-app/
├── app/                    # App Router (v13+)
│   ├── layout.tsx         # 根布局
│   ├── page.tsx           # 主页
│   ├── loading.tsx        # 加载UI
│   ├── error.tsx          # 错误UI
│   ├── not-found.tsx      # 404页面
│   ├── global.css         # 全局样式
│   └── [folder]/          # 路由段
├── public/                # 静态资产
├── components/            # React组件
├── lib/                   # 实用函数
├── next.config.js         # Next.js配置
├── package.json
└── tsconfig.json

路由

文件约定

  • page.tsx - 路由的页面UI
  • layout.tsx - 段和子节点的共享UI
  • loading.tsx - 加载UI(在Suspense中包装页面)
  • error.tsx - 错误UI(在错误边界中包装页面)
  • not-found.tsx - 404 UI
  • route.ts - API端点(路由处理程序)
  • template.tsx - 重新渲染的布局UI
  • default.tsx - 并行路由后备

基本路由

静态路由:

app/
├── page.tsx              → /
├── about/
│   └── page.tsx         → /about
└── blog/
    └── page.tsx         → /blog

动态路由:

// app/blog/[slug]/page.tsx
export default function BlogPost({ params }: { params: { slug: string } }) {
  return <h1>帖子: {params.slug}</h1>
}

捕获所有路由:

// app/shop/[...slug]/page.tsx
export default function Shop({ params }: { params: { slug: string[] } }) {
  return <h1>类别: {params.slug.join('/')}</h1>
}

可选捕获所有:

// app/docs/[[...slug]]/page.tsx
// 匹配 /docs, /docs/a, /docs/a/b 等

路由组

组织路由而不影响URL:

app/
├── (marketing)/          # 组无URL段
│   ├── about/page.tsx   → /about
│   └── blog/page.tsx    → /blog
└── (shop)/
    ├── products/page.tsx → /products
    └── cart/page.tsx     → /cart

并行路由

在同一布局中渲染多个页面:

app/
├── @team/               # 槽
│   └── page.tsx
├── @analytics/          # 槽
│   └── page.tsx
└── layout.tsx           # 使用两个槽
// app/layout.tsx
export default function Layout({
  children,
  team,
  analytics,
}: {
  children: React.ReactNode
  team: React.ReactNode
  analytics: React.ReactNode
}) {
  return (
    <>
      {children}
      {team}
      {analytics}
    </>
  )
}

拦截路由

拦截路由以在模态中显示:

app/
├── feed/
│   └── page.tsx
├── photo/
│   └── [id]/
│       └── page.tsx
└── (..)photo/           # 拦截 /photo/[id]
    └── [id]/
        └── page.tsx

布局

根布局(必需)

// app/layout.tsx
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}

嵌套布局

// app/dashboard/layout.tsx
export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <section>
      <nav>仪表板导航</nav>
      {children}
    </section>
  )
}

布局特性:

  • 跨多个页面共享
  • 导航时保留状态
  • 导航时不重新渲染
  • 可以获取数据

服务器和客户端组件

服务器组件(默认)

app/中的组件默认为服务器组件:

// app/page.tsx(服务器组件)
async function getData() {
  const res = await fetch('https://api.example.com/data')
  return res.json()
}

export default async function Page() {
  const data = await getData()
  return <div>{data.title}</div>
}

优点:

  • 在服务器上获取数据
  • 直接访问后端资源
  • 将敏感数据保留在服务器上
  • 减少客户端JavaScript
  • 改善初始页面加载

限制:

  • 不能使用钩子(useState, useEffect)
  • 不能使用浏览器API
  • 不能添加事件监听器

客户端组件

'use client'指令标记组件:

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

使用客户端组件用于:

  • 交互式UI(onClick, onChange)
  • 状态管理(useState, useReducer)
  • 效果(useEffect, useLayoutEffect)
  • 浏览器API(localStorage, navigator)
  • 自定义钩子
  • React类组件

组合模式

// app/page.tsx(服务器组件)
import { ClientComponent } from './client-component'

export default function Page() {
  return (
    <div>
      <h1>服务器渲染内容</h1>
      <ClientComponent />
    </div>
  )
}

数据获取

服务器组件数据获取

// app/posts/page.tsx
async function getPosts() {
  const res = await fetch('https://api.example.com/posts', {
    next: { revalidate: 3600 } // 每小时重新验证
  })

  if (!res.ok) throw new Error('获取失败')

  return res.json()
}

export default async function PostsPage() {
  const posts = await getPosts()

  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}

缓存策略

强制缓存(默认):

fetch('https://api.example.com/data', { cache: 'force-cache' })

不存储(动态):

fetch('https://api.example.com/data', { cache: 'no-store' })

重新验证:

fetch('https://api.example.com/data', {
  next: { revalidate: 3600 } // 秒
})

基于标签的重新验证:

fetch('https://api.example.com/data', {
  next: { tags: ['posts'] }
})

// 在其他地方重新验证:
import { revalidateTag } from 'next/cache'
revalidateTag('posts')

并行数据获取

async function getData() {
  const [posts, users] = await Promise.all([
    fetch('https://api.example.com/posts').then(r => r.json()),
    fetch('https://api.example.com/users').then(r => r.json()),
  ])

  return { posts, users }
}

顺序数据获取

async function getData() {
  const post = await fetch(`https://api.example.com/posts/${id}`).then(r => r.json())
  const author = await fetch(`https://api.example.com/users/${post.authorId}`).then(r => r.json())

  return { post, author }
}

路由处理程序(API路由)

基本路由处理程序

// app/api/hello/route.ts
export async function GET(request: Request) {
  return Response.json({ message: 'Hello' })
}

export async function POST(request: Request) {
  const body = await request.json()
  return Response.json({ received: body })
}

动态路由处理程序

// app/api/posts/[id]/route.ts
export async function GET(
  request: Request,
  { params }: { params: { id: string } }
) {
  const post = await getPost(params.id)
  return Response.json(post)
}

export async function DELETE(
  request: Request,
  { params }: { params: { id: string } }
) {
  await deletePost(params.id)
  return new Response(null, { status: 204 })
}

请求助手

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url)
  const id = searchParams.get('id')

  const cookies = request.headers.get('cookie')

  return Response.json({ id })
}

响应类型

// JSON
return Response.json({ data: 'value' })

// 文本
return new Response('Hello', { headers: { 'Content-Type': 'text/plain' } })

// 重定向
return Response.redirect('https://example.com')

// 状态码
return new Response('未找到', { status: 404 })

导航

Link组件

import Link from 'next/link'

export default function Page() {
  return (
    <>
      <Link href="/about">关于</Link>
      <Link href="/blog/post-1">帖子1</Link>
      <Link href={{ pathname: '/blog/[slug]', query: { slug: 'post-1' } }}>
        帖子1(替代方式)
      </Link>
    </>
  )
}

useRouter钩子(客户端)

'use client'

import { useRouter } from 'next/navigation'

export function NavigateButton() {
  const router = useRouter()

  return (
    <button onClick={() => router.push('/dashboard')}>
      仪表板
    </button>
  )
}

路由器方法:

  • router.push(href) - 导航到路由
  • router.replace(href) - 替换当前历史
  • router.refresh() - 刷新当前路由
  • router.back() - 向后导航
  • router.forward() - 向前导航
  • router.prefetch(href) - 预取路由

编程导航(服务器)

import { redirect } from 'next/navigation'

export default async function Page() {
  const session = await getSession()

  if (!session) {
    redirect('/login')
  }

  return <div>受保护内容</div>
}

元数据与SEO

静态元数据

// app/page.tsx
import { Metadata } from 'next'

export const metadata: Metadata = {
  title: '我的页面',
  description: '页面描述',
  keywords: ['nextjs', 'react'],
  openGraph: {
    title: '我的页面',
    description: '页面描述',
    images: ['/og-image.jpg'],
  },
  twitter: {
    card: 'summary_large_image',
    title: '我的页面',
    description: '页面描述',
    images: ['/twitter-image.jpg'],
  },
}

export default function Page() {
  return <div>内容</div>
}

动态元数据

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

  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      images: [post.coverImage],
    },
  }
}

元数据文件

  • favicon.ico, icon.png, apple-icon.png - 网站图标
  • opengraph-image.png, twitter-image.png - 社交图像
  • robots.txt - 机器人文件
  • sitemap.xml - 站点地图

图像优化

Image组件

import Image from 'next/image'

export default function Page() {
  return (
    <>
      {/* 本地图像 */}
      <Image
        src="/profile.png"
        alt="个人资料"
        width={500}
        height={500}
      />

      {/* 远程图像 */}
      <Image
        src="https://example.com/image.jpg"
        alt="远程"
        width={500}
        height={500}
      />

      {/* 响应式填充 */}
      <div style={{ position: 'relative', width: '100%', height: '400px' }}>
        <Image
          src="/hero.jpg"
          alt="英雄"
          fill
          style={{ objectFit: 'cover' }}
        />
      </div>

      {/* 优先级加载 */}
      <Image
        src="/hero.jpg"
        alt="英雄"
        width={1200}
        height={600}
        priority
      />
    </>
  )
}

图像属性:

  • src - 图像路径(本地或URL)
  • alt - 替代文本(必需)
  • width, height - 尺寸(必需,除非填充)
  • fill - 填充父容器
  • sizes - 响应式尺寸
  • quality - 1-100(默认75)
  • priority - 预加载图像
  • placeholder - ‘blur’ | ‘empty’
  • blurDataURL - 模糊的数据URL

远程图像配置

// next.config.js
module.exports = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'example.com',
        pathname: '/images/**',
      },
    ],
  },
}

字体优化

Google字体

// app/layout.tsx
import { Inter, Roboto_Mono } from 'next/font/google'

const inter = Inter({
  subsets: ['latin'],
  display: 'swap',
})

const robotoMono = Roboto_Mono({
  subsets: ['latin'],
  display: 'swap',
  variable: '--font-roboto-mono',
})

export default function RootLayout({ children }) {
  return (
    <html lang="en" className={`${inter.className} ${robotoMono.variable}`}>
      <body>{children}</body>
    </html>
  )
}

本地字体

import localFont from 'next/font/local'

const myFont = localFont({
  src: './fonts/my-font.woff2',
  display: 'swap',
  variable: '--font-my-font',
})

加载状态

加载文件

// app/dashboard/loading.tsx
export default function Loading() {
  return <div>加载仪表板中...</div>
}

流式与Suspense

// app/page.tsx
import { Suspense } from 'react'

async function Posts() {
  const posts = await getPosts()
  return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>
}

export default function Page() {
  return (
    <div>
      <h1>我的帖子</h1>
      <Suspense fallback={<div>加载帖子中...</div>}>
        <Posts />
      </Suspense>
    </div>
  )
}

错误处理

错误文件

// app/error.tsx
'use client'

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  return (
    <div>
      <h2>出错了!</h2>
      <p>{error.message}</p>
      <button onClick={() => reset()}>重试</button>
    </div>
  )
}

全局错误

// app/global-error.tsx
'use client'

export default function GlobalError({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  return (
    <html>
      <body>
        <h2>出错了!</h2>
        <button onClick={() => reset()}>重试</button>
      </body>
    </html>
  )
}

未找到

// app/not-found.tsx
export default function NotFound() {
  return (
    <div>
      <h2>404 - 未找到</h2>
      <p>无法找到请求的资源</p>
    </div>
  )
}

// 编程触发
import { notFound } from 'next/navigation'

export default async function Page({ params }) {
  const post = await getPost(params.id)

  if (!post) {
    notFound()
  }

  return <div>{post.title}</div>
}

中间件

// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  // 身份验证检查
  const token = request.cookies.get('token')

  if (!token) {
    return NextResponse.redirect(new URL('/login', request.url))
  }

  // 添加自定义标头
  const response = NextResponse.next()
  response.headers.set('x-custom-header', 'value')

  return response
}

export const config = {
  matcher: ['/dashboard/:path*', '/api/:path*'],
}

环境变量

# .env.local
DATABASE_URL=postgresql://...
NEXT_PUBLIC_API_URL=https://api.example.com
// 仅服务器端
const dbUrl = process.env.DATABASE_URL

// 客户端和服务器(NEXT_PUBLIC_前缀)
const apiUrl = process.env.NEXT_PUBLIC_API_URL

配置

next.config.js

/** @type {import('next').NextConfig} */
const nextConfig = {
  // React严格模式
  reactStrictMode: true,

  // 图像域
  images: {
    remotePatterns: [
      { protocol: 'https', hostname: 'example.com' },
    ],
  },

  // 重定向
  async redirects() {
    return [
      {
        source: '/old-page',
        destination: '/new-page',
        permanent: true,
      },
    ]
  },

  // 重写
  async rewrites() {
    return [
      {
        source: '/api/:path*',
        destination: 'https://api.example.com/:path*',
      },
    ]
  },

  // 标头
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          { key: 'X-Frame-Options', value: 'DENY' },
        ],
      },
    ]
  },

  // 环境变量
  env: {
    CUSTOM_KEY: 'value',
  },
}

module.exports = nextConfig

最佳实践

  1. 使用服务器组件:默认为服务器组件,仅在需要时使用客户端组件
  2. 优化图像:始终使用next/image进行自动优化
  3. 元数据:为SEO设置适当的元数据
  4. 加载状态:用Suspense提供加载UI
  5. 错误处理:实现错误边界
  6. 路由处理程序:用于API端点而不是单独的后端
  7. 缓存:利用内置缓存策略
  8. 布局:使用嵌套布局共享UI
  9. TypeScript:启用TypeScript以确保类型安全
  10. 性能:使用priority加载首屏图像,懒加载非首屏图像

常见模式

受保护路由

// app/dashboard/layout.tsx
import { redirect } from 'next/navigation'
import { getSession } from '@/lib/auth'

export default async function DashboardLayout({ children }) {
  const session = await getSession()

  if (!session) {
    redirect('/login')
  }

  return <>{children}</>
}

数据突变(服务器操作)

// app/actions.ts
'use server'

import { revalidatePath } from 'next/cache'

export async function createPost(formData: FormData) {
  const title = formData.get('title')

  await db.post.create({ data: { title } })

  revalidatePath('/posts')
}

// app/posts/new/page.tsx
import { createPost } from '@/app/actions'

export default function NewPost() {
  return (
    <form action={createPost}>
      <input name="title" type="text" required />
      <button type="submit">创建</button>
    </form>
  )
}

静态生成

// 为动态路由生成静态参数
export async function generateStaticParams() {
  const posts = await getPosts()

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

export default async function Post({ params }) {
  const post = await getPost(params.slug)
  return <article>{post.content}</article>
}

部署

Vercel(推荐)

# 安装Vercel CLI
npm i -g vercel

# 部署
vercel

自托管

# 构建
npm run build

# 启动生产服务器
npm start

要求:

  • Node.js 18.17或更高版本
  • output: 'standalone'在next.config.js中(可选,减少大小)

Docker

FROM node:18-alpine AS base

FROM base AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static

EXPOSE 3000
CMD ["node", "server.js"]

故障排除

常见问题

  1. 水合错误

    • 确保服务器和客户端渲染相同内容
    • 检查服务器组件中是否有仅浏览器代码
    • 验证没有基于浏览器API的条件渲染
  2. 图像未加载

    • next.config.js中添加远程域
    • 检查图像路径(使用前导/表示公共路径)
    • 验证提供宽度和高度
  3. API路由404

    • 检查文件名为route.ts/js而非index.ts
    • 验证导出名为GET/POST而非默认导出
    • 确保在app/api/目录中
  4. "use client"错误

    • 为使用钩子的组件添加'use client'
    • 在服务器组件中导入客户端组件,反之不行
    • 检查事件处理程序有'use client'
  5. 元数据未更新

    • 清除浏览器缓存
    • 检查元数据导出命名正确
    • 验证异步generateMetadata返回Promise<Metadata>

资源

实施清单

构建Next.js应用时:

  • [ ] 用create-next-app创建项目
  • [ ] 配置TypeScript和ESLint
  • [ ] 设置带元数据的根布局
  • [ ] 实现路由结构
  • [ ] 添加加载和错误状态
  • [ ] 配置图像优化
  • [ ] 设置字体优化
  • [ ] 实现数据获取模式
  • [ ] 根据需要添加API路由
  • [ ] 配置环境变量
  • [ ] 根据需要设置中间件
  • [ ] 优化生产构建
  • [ ] 在生产模式下测试
  • [ ] 配置部署平台
  • [ ] 设置监控和分析