Skip to content

linlinw5/nextjs_tutorial

Repository files navigation

Next.js 系列 —— 从 React 到全栈

这本教程,始于 2025 年末我对 Next.js 的一次摸索。

当时的记录是零散的:一段代码、一个疑问、一点理解的偏差,夹杂在学习的时间缝隙里。后来借助 Claude 的整理,这些片段被重新串联起来,逐渐显出轮廓——像把一张未完成的草图,慢慢描出结构与光影。

于是,它成了现在的样子:一份回望,也是一条路径。

学习 Next.js 时,人们很容易走进一个不易察觉的惯性:把它当作"React 的延伸",用熟悉的方式继续书写组件。页面可以运行,逻辑也似乎成立,但很多关键的问题却被轻轻绕开:服务端组件与客户端组件的边界究竟在哪里?Server Action 是在补足什么?缓存,是优化,还是隐患?又该在什么时候让它失效?

这些问题,不会在"能跑起来"的那一刻得到答案。

所以,这本教程选择从更底层的分野出发:客户端渲染,与服务端渲染。

你会先沿着一条熟悉的路径前行:用客户端组件完成一个博客的增删改查;然后转身,从另一侧重新走一遍,用服务端组件重写同样的功能。当两条路径在终点相遇,差异不再需要解释,它会自然显现:有的更直接,有的更克制;有的依赖浏览器,有的回归服务器;而取舍,也在此之间慢慢变得具体。


目录


第一阶段:Next.js 基础

理解 Next.js,先要理解它做了什么:用文件系统替代路由配置,用约定好的文件名(page.tsxlayout.tsxroute.ts)赋予不同功能。这个阶段用两章把这套基础规则讲清楚,后面所有章节都建立在它之上。


第 1 章:路由与布局(01_getting_start

用一个个人博客网站演示 Next.js 的文件系统路由,把"文件夹即路由"这个核心规则从简单到复杂走一遍。最后用 Route Groups 解决"同层级页面需要完全不同的顶层 layout"这个看似绕不过去的问题。

  • 文件系统路由:app/about/page.tsx/about,不需要任何路由配置
  • layout.tsx:包裹 page.tsx 的共享 UI,跨页面跳转时不重新渲染
  • 动态路由 [id]:方括号文件夹,通过 await params 读取动态片段
  • 嵌套 layout:影响范围由所在目录决定,外层不动,内层独立
  • next/link:客户端导航,避免整页刷新,自动预加载链接目标
  • Route Groups (name):括号文件夹,不参与 URL,解决同层级多套 layout 并存的问题

第 2 章:API Route(02_api_route

Next.js 不只是前端框架,它允许在同一个项目里写后端 API。这一章用 Route Handler 实现一套完整的博客 CRUD,数据库用 SQLite + Drizzle ORM,入参校验用 Zod。这套 API 会一直被后续章节复用。

  • Route Handler:route.ts 文件,export 函数名即 HTTP 方法(GETPOSTPUTDELETE
  • Drizzle ORM:用 schema.ts 描述表结构,TypeScript 类型自动推断,不需要手写接口
  • db/ 目录分层:schema.ts 定义结构、client.ts 管理连接、blogs.ts 集中增删改查
  • Zod 运行时校验:TypeScript 类型只在编译期存在,API 入口必须对外来数据做运行时验证
  • HTTP 语义:201 Created / 204 No Content / 400 Bad Request / 404 Not Found 各自的用法

第二阶段:渲染模式

Next.js 最核心的概念是"服务端组件"和"客户端组件"——但这两种模式各自的特点和适用场景,很难从文档里直接读懂。这个阶段的策略是:先用客户端组件完整做一遍,再用服务端组件重做一遍,对比看差异。


第 3 章:客户端渲染(03_client_rendering

"use client" + useEffect + useState 实现博客的完整增删改查。这是最接近"普通 React"的写法——先渲染空壳,挂载后异步获取数据,数据回来再触发重渲染。这种模式你会熟悉,也会感受到它的繁琐,为下一章做铺垫。

  • "use client":客户端组件的声明方式,Next.js 里所有组件默认是服务端组件
  • useEffect + useState 数据获取:固定套路,先空壳后填充
  • useParams():客户端组件读取动态路由参数的方式
  • 手动 loading 状态:if (loading) return <Spinner />
  • useRouter().push():客户端组件里的编程式导航

第 4 章:服务端渲染(04_server_rendering

用服务端组件重写第三章的博客增删改查。代码量明显更少:组件函数加 async,数据直接在函数体里 await,拿到数据才渲染,一步到位。loading.tsxerror.tsx 自动接管加载和错误状态,不再需要手动维护。

  • async 组件:服务端组件可以直接 await,数据准备好了才渲染
  • await params:服务端组件读取路由参数的方式,Next.js 16 中 params 是 Promise
  • Server Action:带 "use server" 指令的 async 函数,传给 <form action={...}>,在服务器上处理表单提交
  • defaultValue:非受控表单的预填充,替代受控组件的 value + onChange
  • loading.tsx:基于 React Suspense,服务端组件 await 期间自动显示
  • error.tsx:错误边界,服务端组件 throw 时自动接管,必须是客户端组件
  • notFound():语义化的 404 处理,比 throw new Error 更准确
  • 服务端 fetch 必须用绝对路径:Node.js 环境没有"当前域名"的概念

第 5 章:Server Action 进阶(05_server_rendering_2

第四章的 Server Action 有两个缺陷:提交期间没有任何反馈,请求失败时也没有错误提示。这一章用 useActionState 解决这两个问题,同时通过三个递进的 Action 模式展示 useActionState 从简单到复杂的完整用法。

  • useActionState:React 19 的 hook,让 Server Action 的返回值能渲染到页面上
  • ActionState 类型设计:status: "error" | "success" | "confirm" | null + 可选 message
  • prevState 参数:Server Action 新签名的第一个参数,大多数时候只是占位——但 deleteAction 用它实现了二次确认
  • 三种 Action 模式:① 只返回错误、成功由服务端 redirect;② 成功和失败都返回状态、前端 useEffect 延迟跳转;③ 读取 prevState 实现二次确认
  • isPendinguseActionState 第三个返回值,Action 执行期间为 true,用于禁用按钮和切换文字
  • actions.ts + types.ts:文件级 "use server" 只能导出 async 函数,常量和类型必须放到独立文件
  • revalidatePath:数据变更后主动通知 Next.js 缓存失效,只在 Server Action 和 Route Handler 里有效

第三阶段:认证

认证是真实应用里不可绕过的部分。这个阶段用三章依次呈现三种架构:手写 JWT、前后端分离的 Better Auth、以及全栈一体化的 Better Auth。每一章都是对前一章的反思:你会亲眼看到什么是"手写解决的",什么是"框架封装掉的",以及"分离部署"和"全栈整合"各自意味着什么取舍。


第 6 章:JWT 认证(06_jwt

用 Fastify 搭建一个独立的认证后端,手写 JWT 签发、验证、刷新的完整流程。Next.js 前端通过 rewrites() 代理 API 请求,绕开 CORS,用 httpOnly cookie 存 token。这一章故意不用认证库——把所有细节暴露在外,让你清楚认证到底在处理哪些问题。

  • Fastify 后端:独立进程(端口 4000),@fastify/jwt 签发 token,@fastify/cookie 管理 httpOnly cookie
  • 双 token 机制:access token(短效,Bearer 验证)+ refresh token(长效,存 cookie,静默续期)
  • next/rewrites():Next.js 服务端代理,前端请求 /api/* 透明转发到后端,彻底绕开浏览器 CORS 限制
  • JWT 的局限:token 一旦签发无法撤销;用户注销只能清客户端 cookie,服务端 token 仍然有效直到过期
  • REST Client 测试.http 文件测试 API,手动管理 token(对比第七章 cookie 自动携带的差异)

第 7 章:Better Auth + Fastify(07_betterauth

把第六章的手写 JWT 替换为 Better Auth,后端仍然是独立的 Fastify 服务,前端仍然是独立的 Next.js。通过对比,清楚看到认证库封装了哪些细节:session 存数据库、token 自动续期、密码哈希、schema 自动生成。

  • Session-based vs JWT:session 存服务端数据库,可随时撤销;JWT 无状态,无法主动失效
  • Drizzle + SQLite:Better Auth 用 @better-auth/cli generate 自动生成 schema,drizzle-kit push 同步建表
  • Fastify 桥接fromNodeHeaders 把 Node.js headers 转成 Web API Headers,让 Better Auth handler 能接收 Fastify 请求
  • CSRF 保护:Better Auth 的 POST 请求需要 Origin 头,REST Client 测试时需要手动添加
  • 三步启动db:generate-schemadb:pushdev,每步在做什么、为什么这个顺序
  • 两种 session 读取GET /api/auth/get-session(内置端点)vs GET /api/me(自定义受保护路由,用 auth.api.getSession() 验证)

第 8 章:Better Auth + Next.js 全栈(08_betterauth_2

把第七章的 Fastify 后端去掉,将 Better Auth 直接集成进 Next.js。认证逻辑变成一个三行的 Route Handler,Server Component 可以零网络请求读取 session,proxy.ts 在请求到达页面前就完成鉴权拦截。

本章同时提供四个对比页面,横向展示客户端与服务端两种处理方式的差异:

路由 方式 核心 API
/login/c 客户端组件 authClient.signIn.email()
/login/s Server Component + Server Action auth.api.signInEmail() + nextCookies plugin
/dashboard/c 客户端组件 authClient.getSession() → HTTP fetch
/dashboard/s Server Component auth.api.getSession({ headers: await headers() }) → 直接查库
  • toNextJsHandler:三行代码把 Better Auth 挂进 Next.js Route Handler,替代第七章 40 行的 Fastify 桥接代码
  • proxy.ts:Next.js 16 把 middleware.ts 改名为 proxy.ts,函数名改为 proxy;在页面渲染前拦截未登录请求,无闪屏
  • Server Component 读 sessionauth.api.getSession({ headers: await headers() }),cookie → DB 查询,同进程完成,零 HTTP
  • Server Action 登录auth.api.signInEmail() 在服务端直接执行,nextCookies plugin 负责把 Set-Cookie 写入响应
  • authClientcreateAuthClient() 提供类型安全的客户端方法,替代裸 fetch

后续计划

  • 缓存深入revalidateTag、按时间过期的 ISR、generateStaticParams 静态预生成——第五章只触及了缓存的冰山一角
  • Metadata APIgenerateMetadata,SEO 标签的正确处理方式

许可

本文档及相关代码以 MIT 协议发布。

Author: Linlin Wang

Contact: wanglinlin.cn@gmail.com

About

A Next.js tutorial based on practical projects.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages