0. 引言

Next.js 是一个基于 React 的服务端渲染(SSR)框架,它不仅支持静态生成(SSG),还内置了强大的路由系统,极大简化了 React 应用的开发流程。

1. 入门

1.1 项目创建

创建Next.js应用最简单的方法就是使用create-next-app(要求nodejs 18.18+): npx create-next-app@latest

使用pnpm可以使用命令: pnpm dlx create-next-app@latest,或者创建一个使用Tailwind V4和React 19的项目:pnpm create next-app@canary --tailwind --eslint --typescript --app --no-src-dir

1.2 路由模式(Router)

Next.js包含两种路由模式,App RouterPage Router,对应不同的目录结构和特性。

特性Page RouterApp Router
目录位置pages/ 目录app/ 目录
路由定义通过文件和文件夹自动映射路由通过文件夹和 page.tsx 组件定义路由
数据获取getStaticProps, getServerSideProps, getStaticPaths直接在组件中使用 async 函数(如 fetch)支持服务器组件
服务器组件支持不支持原生支持服务器组件(Server Components)
嵌套路由通过嵌套文件夹实现通过嵌套文件夹和布局(layout.tsx)实现
布局复用需要手动实现原生支持布局复用
服务器端渲染(SSR)支持支持,且更灵活

Page Router示例:

1
2
3
4
/pages
  /about.js           // 路由 /about
  /blog
    /[id].js          // 动态路由 /blog/:id

App Router示例:

1
2
3
4
5
6
7
/app
  /about
    /page.tsx         // 路由 /about
  /blog
    /[id]
      /page.tsx       // 动态路由 /blog/:id
  /layout.tsx         // 共享布局

目前最新版本的nextjs推荐使用App Router的方式, 本文后续也以App Router的方式进行介绍

1.3 Layout

app目录中的layout.tsx是项目的root layout, 是必须得,且必须包含<html><body>标签。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}

1.4 public目录

项目根目录下可选加一个public目录,通常用来存放一些静态资产例如图片、字体等。在public中的文件可以使用base URL /来进行引用。

1
2
3
4
5
import Image from 'next/image'
 
export default function Page() {
  return <Image src="/profile.png" alt="Profile" width={100} height={100} />
}

1.5 设置绝对路径imports和module path别名

Next.js内置支持pathsbaseUrl选项,这些通常在tsconfig.json中设置。

1
2
3
4
5
6
7
8
9
{
  "compilerOptions": {
    "baseUrl": "src/",  // 设置baseUrl为src
    "paths": {
      "@/styles/*": ["styles/*"],
      "@/components/*": ["components/*"]
    }
  }
}

1.6 项目结构

典型的Next.js项目结构如下:

file namedescription
appApp Router
publicStatic assets to be served
next.config.jsConfiguration file for Next.js
package.jsonProject dependencies and scripts
instrumentation.tsOpenTelemetry and Instrumentation file
middleware.tsNext.js request middleware
.envEnvironment variables
.env.localLocal environment variables
.env.productionProduction environment variables
.env.developmentDevelopment environment variables
.eslintrc.jsonConfiguration file for ESLint
.gitignoreGit files and folders to ignore
next-env.d.tsTypeScript declaration file for Next.js
tsconfig.jsonConfiguration file for TypeScript

2. 组件

2.1 服务端和客户端组件

默认情况,layouts和pages是Server Components, 允许在服务器上获取数据并渲染部分UI, 并且可以选择缓存结果然后将其流式传输到客户端。当您需要交互功能或浏览器 API 时,可以使用Client Components客户端组件。

什么时候选择客户端组件Client Components?

  • 状态和事件处理程序,例如: onChange, onClick
  • 生命周期逻辑Lifecycle logic, 例如:useEffect
  • 仅限于浏览器的API, 例如: localStorage, window
  • Custom Hooks

什么时候选择服务器组件Server Components?

  • 从API或者数据库获取数据
  • 使用API密钥、令牌等,且不暴露给客户
  • 减少发送到浏览器的Javascript数量
  • 优化首次内容绘制(First Contentful Paint, FCP), 并将内容逐步传输到客户端

下面的示例中:<Page>是一个服务器组件,它获取的数据并按照props传递到<LinkButton>处理客户端交互。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// app/[id]/page.tsx
import LikeButton from '@/app/ui/like-button'
import { getPost } from '@/lib/data'
 
export default async function Page({ params }: { params: { id: string } }) {
  const post = await getPost(params.id)
 
  return (
    <div>
      <main>
        <h1>{post.title}</h1>
        {/* ... */}
        <LikeButton likes={post.likes} />
      </main>
    </div>
  )
}
1
2
3
4
5
6
7
8
// app/ui/like-button.tsx
'use client'
 
import { useState } from 'react'
 
export default function LikeButton({ likes }: { likes: number }) {
  // ...
}

从上面的示例中,"use clint"这个指令表示创建客户端组件,一旦文件被文件标记为 “use client” , 其所有导入和子组件都被视为 client bundle 的一部分,不需要将指令添加到用于 Client 端的每个组件中。

如果希望减少客户端Javascript包的大小,尽量仅在必要的组件上使用"use client"指令,而不是将UI的大部分标记为客户端组件。例如:下面的<Layout>组件主要包含静态元素,仅其中的搜索栏<Search>是交互式的,因此将搜索栏声明为Client Components,而其余保持为Server Components

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// Client Component
import Search from './search'
// Server Component
import Logo from './logo'
 
// Layout is a Server Component by default
export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <>
      <nav>
        <Logo />
        <Search />
      </nav>
      <main>{children}</main>
    </>
  )
}

2.2 工作原理

2.2.1 服务器端

Next.js使用React的API来编排渲染,渲染工作按照各个路由段(layouts and pages)拆分为块:其中: Server Components被渲染成一种特殊的数据格式,称为React Server Component Payload(RSC Payload),Client Components和RSC Payload用于预渲染HTML.

RSC Payload: RSC Payload是用于渲染完成的React服务器组件树的紧凑二进制表示(compact binary representation), React可以在客户端使用这个数据来更新浏览器DOM.RSC Payload包含:Server Components的渲染结果、客户端组件应呈现位置的占位符以及其对Javascript文件的引用,从Server Component传递到Client Component的任何props

2.2.2 客户端

在客户端上,HTML用于立即向用户展示路由的快速非交互式预览,RSC Payload用于协调 Client 和Server Component树, JavaScript用于激活客户端组件并使应用程序具有交互性。

hydration(补水):表示React 将事件处理程序附加到 DOM 的过程,以使静态 HTML 具有交互性

2.3 数据传递

可以使用props将数据从Server Components传递到Client Components(如2.1中的<LikeButton>组件), 也可以使用use Hook进行流式的传值。

3. 样式

3.1 tailwind

4. Auth.js

Auth.js是一个基于标准Web API的运行时无关库,可以与多个现代Javascript框架集成,提供了易于上手、方便扩展且私密安全的身份验证体验。

后续的介绍基于next-auth@5.0.0-beta及以上的版本

使用Auth.js验证用户身份的方法有4种:

  1. OAuth身份验证(使用Google, Github等登录)
  2. 魔术链接Magic Links(电子邮件提供商如Forward Email, Resend等)
  3. Credentials (用户名和密码)
  4. WebAuthn(Passkeys, etc…)

Hello, Auth.js

初始化

依赖安装:pnpm add next-auth@beta 初始化必须的环境变量AUTH_SECRET: pnpm dlx auth secret, 这会添加对应的环境变量到.env文件,或者Next.js中的.env.local

基础配置

通常会在项目中创建auth.ts文件来进行Auth.js的配置,在文件中我们将配置的内容传递给框架特定的初始化函数,然后导出路由处理程序、登录和退出方法等。

1
2
3
4
5
import NextAuth from "next-auth"
 
export const { handlers, signIn, signOut, auth } = NextAuth({
  providers: [],
})

然后在app/api/auth/[...nextauth]中创建对应的Route Handler。

1
2
3
// app/api/auth/[...nextauth]/route.ts
import { handlers } from "@/auth" // Referring to the auth.ts we just created
export const { GET, POST } = handlers

最后添加可选的中间件以保持会话处于活动状态,这将在每次调用时更新会话到期时间。

1
2
// middleware.ts
export { auth as middleware } from "@/auth"

验证

Auth.js推荐使用OAuth服务

Auth.js 默认使用加密的 JSON Web Token(JWT) 将会话保存在 Cookie 中,因此您无需设置数据库。如果希望持久保存用户数据,可以使用我们的官方数据库适配器之一设置数据库,或者创建您自己的数据库。

5. 数据交互

5.1 获取数据Fetching data

Server Components中获取数据常用的方式:

  1. fetch API

如果需要使用fetch获取,需要将组件转换为异步函数,并等待fetch调用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// app/blog/page.tsx
export default async function Page() {
  const data = await fetch('https://api.vercel.app/blog')
  const posts = await data.json()
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}

默认情况下,fetch响应不会被缓存,但是Next.js会预渲染路由,并将输出缓存以提高性能;如果需要启动动态渲染(dynamic rendering),需要使用{cache: 'no-store'}选项。

  1. ORM或者数据库

由于Server Components在服务器上渲染,所以可以安全的使用ORM或者数据库, 将组件转换为异步函数并等待调用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// app/blog/page.tsx
import { db, posts } from '@/lib/db'
 
export default async function Page() {
  const allPosts = await db.select().from(posts)
  return (
    <ul>
      {allPosts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}

Client Components获取数据的方式:

  1. 使用use hook

8. React知识拾遗

8.1 Suspense

OAuth服务配置

Github: 申请地址 github developer settings, 回调地址参考下方进行配置。

默认的回调地址URL应该是类似:[origin]/api/auth/callback/[provider]的格式,例如:

1
2
3
4
5
// Local
http://localhost:3000/api/auth/callback/github
 
// Prod
https://app.company.com/api/auth/callback/github

5. Shadcn

ShadCN/UI 是一组设计精美、可访问的组件和一个代码分发平台,完全开源。

安装方式:pnpm dlx shadcn@canary init 添加组件:pnpm dlx shadcn@canary add button

Shadcn默认使用Tailwind V3, 如果要使用Tailwind V4的话,需要使用shadcn@canary, 否则使用shadcn@latest

添加组件以后可以类似如下进行使用:

1
2
3
4
5
6
7
8
9
import { Button } from "@/components/ui/button"

export default function Home() {
  return (
    <div>
      <Button>Click me</Button>
    </div>
  )
}

上面的初始化代码中会创建一个components.json文件,其中包含项目的配置。其内容类似如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
{
  "$schema": "https://ui.shadcn.com/schema.json",
  "style": "new-york",
  "rsc": false,
  "tsx": true,
  "tailwind": {
    "config": "tailwind.config.ts",
    "css": "src/globals.css",
    "baseColor": "neutral",
    "cssVariables": true,
    "prefix": "tw-"
  },
  "aliases": {
    "components": "@/components",
    "utils": "@/lib/utils",
    "ui": "@/components/ui",
    "lib": "@/lib",
    "hooks": "@/hooks"
  },
  "iconLibrary": "lucide"
}

其中:

  1. schema: 表示文件对应的json schema
  2. style: 表示组件的样式,初始化以后无法更改
  3. tailwind: 表示如何在项目中设置Tailand CSS, 其中config表示对应的 tailwind.config.ts文件所在的路径,对于Tailwind CSS v4, 该字段应该留空css表示将Tailwind CSS导入到项目中的文件路径, baseColor用于为组件生成默认调色板,初始化后无法更改cssVariables表示用CSS变量还是Tailwind CSS utility classes进行主题设置,设置为false表示使用Tailwind CSS utility classes,设置为true表示使用css变量,初始化后无法更改prefix用于Tailwind CSS utility classes的前缀。
  4. rsc表示是否启用对React Server Components的支持,当设置为true时,CLI会自动将use client指令添加到客户端组件。
  5. aliases: shadcn使用这里的配置以及tsconfig.json文件中的paths配置,来保证生成的组件放置在正确的位置