Next.js 全栈开发入门指南(2026 最新版)

2026/4/9 笔记NextJS

# 文档说明

本文档面向Web开发初学者,全程无晦涩术语、代码可直接复制运行。你将系统掌握Next.js的核心定位、解决的行业痛点、与传统开发模式的核心差异,从0搭建项目、吃透三大核心渲染模式、完成「调用第三方API+数据库增删改查」的全栈实战,最终掌握企业级最佳实践与适用场景,看完即可独立开发上线完整项目。


# 一、什么是Next.js?

Next.js 是由Vercel公司开发、Meta官方推荐的React全栈Web开发框架

它把前端页面渲染、后端服务逻辑、路由系统、数据获取、性能优化、部署上线等全流程能力整合到一套代码中,彻底打破了「前端只管页面、后端只管接口」的传统壁垒,让你只用React语法,就能开发出高性能、SEO友好、安全可靠的全栈Web应用。

简单说:一套React代码,搞定前端+后端,不用再分开写前端项目和后端接口项目


# 二、传统Web开发的核心痛点(Next.js的解决之道)

在Next.js普及之前,Web开发主要分为两种模式,均存在无法规避的核心痛点,这也是Next.js诞生的核心原因。

# 2.1 两种传统开发模式的致命痛点

传统开发模式 核心特点 无法解决的痛点
传统服务端渲染(PHP/JSP/Java模板) 页面在服务端生成HTML返回给浏览器,前后端代码高度耦合 1. 前后端强绑定,前端改页面需要后端配合,开发效率极低;
2. 页面跳转全量刷新,用户体验差;
3. 无法使用现代React/Vue前端生态,交互能力弱
前后端分离(React SPA + 独立后端API) 前端单独写单页应用,通过fetch/axios调用后端接口,前后端完全分离 1. 首屏白屏严重:需要先下载完整JS包,再渲染页面、请求数据,低端机/弱网体验极差;
2. SEO天生残疾:页面内容由JS动态渲染,搜索引擎爬虫无法抓取有效内容;
3. 开发成本高:一个简单的用户新增功能,需要前端写页面、后端写接口、双方联调、处理跨域/错误/loading,代码量翻倍;
4. 安全风险高:前端无法直接操作数据库,所有数据操作依赖接口,接口多了极易出现权限遗漏、数据越权问题;
5. 运维复杂:前端部署CDN、后端部署服务器,需要配置跨域、HTTPS、缓存,出问题需要两端排查

# 2.2 Next.js 如何一次性解决所有痛点?

针对以上痛点,Next.js给出了一站式解决方案,核心能力如下:

  1. 多渲染模式自由切换:同时支持SSR(服务端渲染)、SSG(静态生成)、ISR(增量静态更新)、CSR(客户端渲染),既解决了SPA的白屏&SEO问题,又保留了现代前端的丝滑交互体验;

  2. Server Actions 全栈能力:无需单独写API接口,直接在React组件中编写服务端函数,即可安全操作数据库、调用私密接口,彻底告别「接口联调地狱」;

  3. 文件式路由系统:文件夹结构=页面路由,无需手动配置路由规则,零配置实现嵌套路由、动态路由、权限路由;

  4. 内置全自动性能优化:图片自动压缩/懒加载、代码自动分割、路由预加载、字体优化,新手也能写出高性能页面;

  5. 服务端代码天然隔离:服务端逻辑永远不会发送到浏览器,数据库密码、API密钥等敏感信息100%安全;

  6. 一体化部署:一套代码一键部署,无需分开维护前后端服务,彻底解决跨域、版本不同步问题。


# 三、Next.js 与传统Web开发的核心优势对比

对比维度 传统前后端分离 传统PHP/JSP服务端渲染 Next.js 全栈框架
首屏加载速度 慢,白屏时间长 较快,跳转全量刷新 极快,服务端返回完整HTML,跳转无刷新
SEO友好度 差,需额外做预渲染改造 良好,内容直接返回 优秀,天然支持所有搜索引擎抓取
开发效率 低,需写接口、联调、处理跨域 低,前后端耦合,改造成本高 极高,一套代码搞定前后端,零接口联调
数据安全性 依赖接口权限校验,易出现漏洞 较好,逻辑在服务端 优秀,服务端代码物理隔离,内置CSRF防护
交互体验 优秀,无刷新跳转 差,页面全量刷新 优秀,内置路由预加载,原生级交互体验
性能优化门槛 高,需手动配置代码分割、图片优化等 低,优化能力有限 极低,框架内置全自动优化,开箱即用
部署运维成本 高,前后端分开部署、配置 中,需配置服务端环境 极低,一键部署,一体化运维
无JS兼容性 完全不可用,页面白屏 可用,交互能力弱 可用,内容正常展示,表单可正常提交

# 四、快速入门:5分钟从0搭建第一个Next.js项目

# 4.1 环境准备

仅需安装 Node.js 18.17及以上版本(官网下载LTS版本,一路默认安装即可),安装完成后在终端执行以下命令,能输出版本号即安装成功:

node -v
1

# 4.2 创建项目

打开终端,进入你想存放项目的文件夹,执行以下创建命令:

npx create-next-app@latest my-first-next-app
1

执行后会出现配置选项,新手一路按回车选择默认配置即可,默认配置包含:TypeScript、ESLint、Tailwind CSS、App Router(最新路由系统)。

# 4.3 启动项目

创建完成后,依次执行以下命令进入项目、启动开发服务器:

# 进入项目文件夹
cd my-first-next-app
# 启动开发服务器
npm run dev
1
2
3
4

启动成功后,打开浏览器访问 http://localhost:3000,看到Next.js欢迎页面,即项目搭建成功!

# 4.4 新手必懂的核心项目结构

my-first-next-app/
├── app/           【核心】路由文件夹,文件结构=页面路由
│   ├── page.tsx   首页,对应访问路径 /
│   ├── layout.tsx 全局布局文件,所有页面共用
│   └── about/     新建about文件夹,对应路由 /about
│       └── page.tsx 关于页面,对应访问路径 /about
├── public/        静态资源文件夹,存放图片、图标等
├── lib/           工具类文件夹,存放数据库连接、工具函数等(需手动创建)
└── package.json   项目依赖配置文件
1
2
3
4
5
6
7
8
9

核心规则app 文件夹下,每一个 page.tsx 文件,都会自动生成一个对应路径的页面,无需手动配置路由,路由分为三类:

  1. 基础规则: app/ 文件夹内部的层级结构 = 网址 URL只有创建了 page.tsx,这个路径才能被访问

  2. 固定路由: 文件夹名不带 [] = 写死的固定网址

    app/about/page.tsx      →  访问 /about
    app/blog/page.tsx       →  访问 /blog
    
    1
    2
  3. 动态路由: 文件夹名带 [] = 可变参数的动态网址

    app/blog/[slug]/page.tsx  →  访问 /blog/1、/blog/abc、/blog/任意值
    
    1

代码目录和路由关系:

你的项目目录               →  浏览器访问地址
└── app/
    ├── page.tsx          →  localhost:3000/           (首页)
    ├── about/
    │   └── page.tsx      →  localhost:3000/about       (固定路由)
    └── blog/
        ├── page.tsx      →  localhost:3000/blog        (固定路由)
        └── [id]/
            └── page.tsx  →  localhost:3000/blog/123    (动态路由)
1
2
3
4
5
6
7
8
9

# 五、Next.js 四大核心渲染模式详解(含代码示例)

渲染模式是Next.js的核心灵魂,它决定了页面何时生成、数据何时获取,直接影响页面性能、SEO和服务器压力。Next.js App Router 原生支持四大核心渲染模式(SSR、SSG、ISR、CSR),无需复杂配置,几行代码即可切换,所有示例均基于公开的文章API,可直接复制运行。

# 5.1 SSG(Static Site Generation 静态站点生成)

# 核心定义

SSG是Next.js默认的渲染模式,也是性能最优的模式。它会在项目构建阶段(执行npm run build时),提前执行数据获取、渲染生成完整的静态HTML文件,部署后所有用户访问都直接返回这个预先生成的静态文件,无需服务端重复执行代码、查询接口/数据库。

特点

  • build一次性生成 HTML
  • 部署后内容不会变
  • 最快、最利于 SEO

# 适用场景

不常更新的静态内容页面:企业官网、个人博客、帮助文档、营销落地页、静态新闻列表。

# 核心优势

  • 访问速度极快:静态文件可直接托管在CDN,用户打开秒开;

  • 零服务器运行压力:无需每次请求执行服务端逻辑,高并发无压力;

  • 极致SEO友好:完整HTML内容直接被搜索引擎抓取;

  • 稳定性极强:无服务端代码执行,不会出现接口超时等运行时错误。

# 完整代码示例

新建文件 app/ssg-posts/page.tsx,写入以下代码:

// app/ssg-posts/page.tsx 对应访问路径 /ssg-posts
// 无任何强制动态配置,Next.js默认使用SSG静态生成
interface Post {
  userId: number;
  id: number;
  title: string;
  body: string;
}

// 构建时执行数据获取,生成静态页面
async function getStaticPosts(): Promise<Post[]> {
  // fetch默认开启强制缓存,构建时拉取数据后永久缓存
  const res = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=10');
  if (!res.ok) throw new Error('数据获取失败');
  return res.json();
}

// 页面组件
export default async function SSGPostsPage() {
  // 构建时获取数据,前端访问直接返回预渲染的HTML
  const posts = await getStaticPosts();

  return (
    <div className="max-w-3xl mx-auto p-6">
      <h1 className="text-3xl font-bold mb-8">SSG静态生成文章列表</h1>
      <p className="mb-6 text-gray-500">本页面在构建时预渲染,所有用户访问均返回静态HTML</p>
      <div className="space-y-4">
        {posts.map((post) => (
          <div key={post.id} className="border rounded-lg p-4 shadow-sm">
            <h2 className="text-xl font-semibold mb-2">{post.title}</h2>
            <p className="text-gray-600">{post.body}</p>
          </div>
        ))}
      </div>
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

# 验证方式

执行 npm run build 打包项目,在打包日志中,该页面会标记为 ○ (Static),即代表SSG静态生成成功。


# 5.2 SSR(Server-Side Rendering 服务端渲染)

# 核心定义

SSR即动态服务端渲染,用户每次发起页面请求时,服务端都会实时执行数据获取、渲染完整的HTML页面,再返回给浏览器。每次请求都会重新执行代码、拉取最新数据。

特点

  • 每次访问都重新生成 HTML
  • 数据永远最新
  • 性能比 SSG/ISR 差

# 适用场景

强实时性、强个性化的动态页面:实时数据仪表盘、个人中心、库存实时变动的商品详情页、需要实时权限校验的页面。

# 核心优势

  • 数据永远最新:每次请求都拉取实时数据,无缓存延迟;

  • SEO友好:完整HTML内容直接返回,搜索引擎可正常抓取;

  • 个性化能力强:可根据用户请求信息(Cookie、登录态)返回个性化内容;

  • 首屏性能优秀:无需等待客户端JS下载执行,直接返回渲染好的页面。

# 完整代码示例

新建文件 app/ssr-posts/page.tsx,写入以下代码:

// app/ssr-posts/page.tsx 对应访问路径 /ssr-posts
// 强制动态渲染,每次请求都重新执行服务端逻辑
interface Post {
  userId: number;
  id: number;
  title: string;
  body: string;
}

// 每次用户请求页面时,都会实时执行该函数拉取最新数据
async function getDynamicPosts(): Promise<Post[]> {
  // cache: 'no-store' 强制不缓存,每次请求都重新调用接口
  const res = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=10', {
    cache: 'no-store'
  });
  if (!res.ok) throw new Error('数据获取失败');
  return res.json();
}

// 页面组件
export default async function SSRPostsPage() {
  // 每次请求都实时获取最新数据
  const posts = await getDynamicPosts();
  // 可获取当前请求时间,验证每次刷新都会更新
  const requestTime = new Date().toLocaleString();

  return (
    <div className="max-w-3xl mx-auto p-6">
      <h1 className="text-3xl font-bold mb-4">SSR服务端渲染文章列表</h1>
      <p className="mb-6 text-gray-500">本页面每次刷新都会重新渲染,当前请求时间:{requestTime}</p>
      <div className="space-y-4">
        {posts.map((post) => (
          <div key={post.id} className="border rounded-lg p-4 shadow-sm">
            <h2 className="text-xl font-semibold mb-2">{post.title}</h2>
            <p className="text-gray-600">{post.body}</p>
          </div>
        ))}
      </div>
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

# 验证方式

  1. 执行 npm run build 打包项目,在打包日志中,该页面会标记为 λ (Dynamic),即代表SSR动态渲染;

  2. 启动生产服务 npm run start,访问页面后多次刷新,可看到页面上的请求时间每次都会更新,验证每次请求都重新渲染。


# 5.3 ISR(Incremental Static Regeneration 增量静态再生成)

# 核心定义

ISR是Next.js的王牌功能,完美结合了SSG的极致性能和SSR的实时性优势。它在构建时预生成静态页面,部署后可按照你设定的时间间隔,在后台自动重新拉取数据、渲染更新静态页面,无需重新构建、重新部署项目。

简单说:静态页面的访问速度,同时支持数据自动定期更新

特点

  • 先 SSG,定时自动刷新
  • 访问快 + 数据能更新
  • revalidate 控制刷新时间

# 适用场景

需要定期更新、但无需实时的页面:新闻资讯列表、电商商品列表、博客文章、论坛帖子列表,访问量高但更新频率不高的内容。

# 核心优势

  • 兼顾速度与新鲜度:既有SSG的静态访问速度,又能定期更新数据;

  • 极低的服务器压力:无论多少用户访问,仅在设定的间隔内执行一次数据更新;

  • 无需重新部署:内容更新无需重新打包构建,后台自动完成;

  • 容错能力强:即使更新时接口出错,仍会返回之前的静态页面,不会影响用户访问。

# 完整代码示例

新建文件 app/isr-posts/page.tsx,写入以下代码:

// app/isr-posts/page.tsx 对应访问路径 /isr-posts
// 增量静态再生成,设置60秒重新验证更新一次数据
interface Post {
  userId: number;
  id: number;
  title: string;
  body: string;
}

// 设定页面缓存有效期,单位:秒
// 60秒内的所有访问,都返回缓存的静态页面;60秒后首次访问,后台自动更新页面
export const revalidate = 60;

// 构建时预执行,后续每60秒自动重新执行更新数据
async function getISRPosts(): Promise<Post[]> {
  const res = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=10');
  if (!res.ok) throw new Error('数据获取失败');
  return res.json();
}

// 页面组件
export default async function ISRPostsPage() {
  const posts = await getISRPosts();
  // 页面生成时间,验证缓存更新
  const generateTime = new Date().toLocaleString();

  return (
    <div className="max-w-3xl mx-auto p-6">
      <h1 className="text-3xl font-bold mb-4">ISR增量静态更新文章列表</h1>
      <p className="mb-6 text-gray-500">本页面每60秒自动更新一次,当前页面生成时间:{generateTime}</p>
      <div className="space-y-4">
        {posts.map((post) => (
          <div key={post.id} className="border rounded-lg p-4 shadow-sm">
            <h2 className="text-xl font-semibold mb-2">{post.title}</h2>
            <p className="text-gray-600">{post.body}</p>
          </div>
        ))}
      </div>
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

# 验证方式

  1. 执行 npm run build 打包项目,在打包日志中,该页面会标记为 ● (ISR),即代表增量静态更新配置成功;

  2. 启动生产服务 npm run start,首次访问页面记录生成时间,后续60秒内刷新,时间不会变化(返回缓存静态页);60秒后再次刷新,页面会更新为最新的生成时间,验证后台自动更新生效。


# 5.4 CSR(Client-Side Rendering 客户端渲染)

# 核心定义

CSR即客户端渲染,是传统React SPA的核心渲染模式。服务端仅返回一个空的HTML模板(包含JS引用),页面渲染、数据获取等逻辑全部在浏览器端(客户端)执行,通过JS动态生成页面内容并插入到HTML中,完成页面展示。

简单说:服务端只给“空壳”,浏览器自己加载JS、获取数据、渲染页面,也是Next.js中需手动开启的渲染模式(标记'use client')。

特点

  • 纯前端渲染,首屏无内容
  • 数据在浏览器里请求
  • 不利于 SEO
  • 无缓存

# 适用场景

强交互、无SEO需求、数据频繁变动的页面:后台管理系统、用户个人中心(非公开内容)、在线编辑器、社交应用动态 feed、游戏类Web应用。

# 核心优势

  • 交互体验极致流畅:页面渲染和数据更新都在客户端完成,无需请求服务端,跳转、操作无延迟;

  • 减轻服务端压力:服务端仅返回静态模板,无需执行渲染和数据查询逻辑,高并发场景下优势明显;

  • 动态交互能力强:支持复杂的用户交互(如拖拽、实时输入反馈、弹窗联动),适配需要大量JS交互的场景;

  • 开发灵活:完全沿用传统React开发模式,熟悉React的开发者可快速上手,无需额外学习服务端渲染逻辑。

# 完整代码示例

新建文件 app/csr-posts/page.tsx,写入以下代码(明确标记客户端渲染,适配复杂交互场景):

// app/csr-posts/page.tsx 对应访问路径 /csr-posts
'use client'; // 标记为客户端组件,强制开启CSR客户端渲染

import { useState, useEffect } from 'react';

interface Post {
  userId: number;
  id: number;
  title: string;
  body: string;
}

export default function CSRPostsPage() {
  // 客户端维护状态,用于交互和数据展示
  const [posts, setPosts] = useState<Post[]>([]);
  const [loading, setLoading] = useState(true);
  const [searchKey, setSearchKey] = useState(''); // 客户端交互状态(搜索)

  // 客户端数据获取,在浏览器端执行
  const fetchPosts = async () => {
    setLoading(true);
    try {
      // 客户端请求API,可根据交互状态(如搜索关键词)动态调整请求参数
      const res = await fetch(`https://jsonplaceholder.typicode.com/posts?q=${searchKey}`);
      if (!res.ok) throw new Error('数据获取失败');
      const data = await res.json();
      setPosts(data.slice(0, 10)); // 截取前10条数据
    } catch (err) {
      console.error('客户端请求失败', err);
    } finally {
      setLoading(false);
    }
  };

  // 组件挂载时获取初始数据,搜索关键词变化时重新获取数据
  useEffect(() => {
    fetchPosts();
  }, [searchKey]);

  return (
    <div className="max-w-3xl mx-auto p-6">
      <h1 className="text-3xl font-bold mb-8">CSR客户端渲染文章列表</h1>
      <p className="mb-6 text-gray-500">本页面在浏览器端渲染,数据获取和页面更新均在客户端完成</p>

      // 客户端交互组件(搜索框),体现CSR交互优势
      <div className="mb-6">
        <input
          type="text"
          placeholder="输入关键词搜索文章..."
          value={searchKey}
          onChange={(e) => setSearchKey(e.target.value)}
          className="w-full px-4 py-2 border rounded-lg shadow-sm"
        />
      </div>

      {loading ? (
        <div className="p-6 text-center">加载中...</div>
      ) : (
        <div className="space-y-4">
          {posts.map((post) => (
            <div key={post.id} className="border rounded-lg p-4 shadow-sm">
              <h2 className="text-xl font-semibold mb-2">{post.title}</h2>
              <p className="text-gray-600">{post.body.substring(0, 100)}...</p>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70

# 验证方式

  1. 执行 npm run dev 启动开发服务,访问 http://localhost:3000/csr-posts,可看到页面先显示“加载中”,随后渲染内容(验证客户端渲染流程);

  2. 打开浏览器“开发者工具”,切换到「Network」面板,刷新页面,可看到服务端仅返回 csr-posts 相关的HTML和JS文件,数据请求(fetch)是在客户端发起的;

  3. 在搜索框输入关键词(如“test”),页面会实时重新请求数据并更新,无需刷新页面,验证客户端交互与动态渲染能力。


# 六、核心实战:2个必学示例(调用第三方API + 数据库增删改查)

以下示例代码均可直接复制运行,全程基于Next.js最新的App Router和Server Components,符合企业级开发规范。

# 示例1:调用第三方API(2种常用方式,新手必懂)

我们以公开的「文章数据API」为例,地址:https://jsonplaceholder.typicode.com/posts,无需申请密钥,可直接调用。

Next.js中调用API分为服务端调用客户端调用优先推荐服务端调用,性能更好、SEO更友好、无跨域问题。

# 方式1:服务端组件调用(推荐,90%场景首选)

Next.js中,没有加 'use client' 标记的组件,默认都是服务端组件,代码只在服务端运行,可直接用 async/await 调用API,无需useEffect、无需处理loading状态。

新建文件 app/posts/page.tsx,写入以下代码:

// app/posts/page.tsx 对应访问路径 /posts
// 无'use client',默认是服务端组件,代码只在服务端运行

// 定义接口返回数据类型
interface Post {
  id: number;
  title: string;
  body: string;
}

// 服务端调用第三方API
async function getPosts(): Promise<Post[]> {
  // 调用第三方API,Next.js默认会缓存结果,避免重复请求
  const res = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=10');
  if (!res.ok) throw new Error('接口请求失败');
  return res.json();
}

// 页面组件,直接async/await获取数据
export default async function PostsPage() {
  // 直接在组件中获取数据,服务端执行,前端看不到接口调用逻辑
  const posts = await getPosts();

  return (
    <div className="max-w-3xl mx-auto p-6">
      <h1 className="text-3xl font-bold mb-8">第三方API文章列表</h1>
      <div className="space-y-4">
        {posts.map((post) => (
          <div key={post.id} className="border rounded-lg p-4 shadow-sm">
            <h2 className="text-xl font-semibold mb-2">{post.title}</h2>
            <p className="text-gray-600">{post.body}</p>
          </div>
        ))}
      </div>
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

保存后,访问 http://localhost:3000/posts,即可看到渲染好的文章列表。

核心优势

  • 页面打开直接显示内容,无白屏、无loading等待;
  • 搜索引擎直接抓取到完整内容,SEO拉满;
  • 接口调用在服务端完成,前端看不到接口地址,无跨域问题;
  • Next.js默认缓存接口结果,重复访问不会重复调用第三方API。

# 方式2:客户端组件调用(仅用于需要实时交互的场景)

如果你的页面需要按钮点击、实时输入等用户交互,需要使用客户端组件,加 'use client' 标记,用法和传统React一致。

新建文件 app/client-posts/page.tsx,写入以下代码:

// app/client-posts/page.tsx 对应访问路径 /client-posts
'use client'; // 标记为客户端组件,代码会发送到浏览器

import { useEffect, useState } from 'react';

interface Post {
  id: number;
  title: string;
  body: string;
}

export default function ClientPostsPage() {
  const [posts, setPosts] = useState<Post[]>([]);
  const [loading, setLoading] = useState(true);

  // 客户端调用API,和传统React用法一致
  useEffect(() => {
    const fetchPosts = async () => {
      try {
        const res = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=10');
        const data = await res.json();
        setPosts(data);
      } catch (err) {
        console.error('请求失败', err);
      } finally {
        setLoading(false);
      }
    };
    fetchPosts();
  }, []);

  if (loading) return <div className="p-6">加载中...</div>;

  return (
    <div className="max-w-3xl mx-auto p-6">
      <h1 className="text-3xl font-bold mb-8">客户端调用API文章列表</h1>
      <div className="space-y-4">
        {posts.map((post) => (
          <div key={post.id} className="border rounded-lg p-4 shadow-sm">
            <h2 className="text-xl font-semibold mb-2">{post.title}</h2>
            <p className="text-gray-600">{post.body}</p>
          </div>
        ))}
      </div>
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

# 示例2:连接数据库,实现完整增删改查(CRUD)

我们使用SQLite作为数据库,无需安装任何数据库服务、无需配置账号密码,开箱即用,新手零门槛。搭配Next.js的Server Actions,无需写任何API接口,即可安全实现数据库增删改查。

# 步骤1:安装依赖

在项目终端执行以下命令,安装数据库依赖:

npm install better-sqlite3
# 类型声明,TypeScript项目需要
npm install -D @types/better-sqlite3
1
2
3

# 步骤2:创建数据库连接工具类

新建 lib/db.ts 文件,写入以下代码,实现数据库连接和表初始化:

// lib/db.ts
import Database from 'better-sqlite3';

// 连接数据库,自动创建db.sqlite3文件,无需手动创建
const db = new Database('db.sqlite3');

// 初始化用户表,第一次运行自动创建,后续运行不会重复创建
db.exec(`
  CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    email TEXT NOT NULL UNIQUE,
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP
  )
`);

export default db;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 步骤3:创建Server Actions,实现增删改查逻辑

新建 app/users/actions.ts 文件,写入以下代码,所有函数都标记为服务端操作,代码只在服务端运行,绝对不会暴露给前端:

// app/users/actions.ts
'use server'; // 标记整个文件的函数都是Server Actions,只在服务端运行

import db from '@/lib/db';
import { revalidatePath } from 'next/cache';

// 1. 新增用户
export async function addUser(formData: FormData) {
  // 从表单中获取数据
  const name = formData.get('name') as string;
  const email = formData.get('email') as string;

  if (!name || !email) throw new Error('姓名和邮箱不能为空');

  try {
    // 执行数据库插入操作
    db.prepare('INSERT INTO users (name, email) VALUES (?, ?)').run(name, email);
    // 刷新页面数据,让用户看到最新的列表
    revalidatePath('/users');
    return { success: true, message: '用户添加成功' };
  } catch (err) {
    console.error('添加用户失败', err);
    return { success: false, message: '邮箱已存在,添加失败' };
  }
}

// 2. 删除用户
export async function deleteUser(id: number) {
  try {
    db.prepare('DELETE FROM users WHERE id = ?').run(id);
    revalidatePath('/users');
    return { success: true };
  } catch (err) {
    console.error('删除用户失败', err);
    return { success: false, message: '删除失败' };
  }
}

// 3. 修改用户
export async function updateUser(formData: FormData) {
  const id = Number(formData.get('id'));
  const name = formData.get('name') as string;

  if (!id || !name) throw new Error('参数错误');

  try {
    db.prepare('UPDATE users SET name = ? WHERE id = ?').run(name, id);
    revalidatePath('/users');
    return { success: true, message: '修改成功' };
  } catch (err) {
    console.error('修改用户失败', err);
    return { success: false, message: '修改失败' };
  }
}

// 4. 查询用户列表
export function getUsers() {
  return db.prepare('SELECT * FROM users ORDER BY create_time DESC').all();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

# 步骤4:创建页面,实现完整的用户管理功能

新建 app/users/page.tsx 文件,写入以下代码,实现表单提交、用户列表展示、修改和删除功能:

// app/users/page.tsx 对应访问路径 /users
import { addUser, deleteUser, getUsers, updateUser } from './actions';

export default function UsersPage() {
  // 服务端直接查询数据库,前端看不到任何数据库逻辑
  const users = getUsers();

  return (
    <div className="max-w-4xl mx-auto p-6">
      <h1 className="text-3xl font-bold mb-8">用户管理系统(数据库增删改查)</h1>

      {/* 新增用户表单,直接绑定Server Action,无需写onSubmit、无需写fetch */}
      <form action={addUser} className="flex gap-3 mb-10 p-4 border rounded-lg shadow-sm">
        <input
          type="text"
          name="name"
          placeholder="请输入姓名"
          className="flex-1 px-3 py-2 border rounded"
          required
        />
        <input
          type="email"
          name="email"
          placeholder="请输入邮箱"
          className="flex-1 px-3 py-2 border rounded"
          required
        />
        <button
          type="submit"
          className="px-6 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
        >
          添加用户
        </button>
      </form>

      {/* 用户列表 */}
      <div className="border rounded-lg overflow-hidden">
        <table className="w-full">
          <thead className="bg-gray-50 border-b">
            <tr>
              <th className="px-4 py-3 text-left">ID</th>
              <th className="px-4 py-3 text-left">姓名</th>
              <th className="px-4 py-3 text-left">邮箱</th>
              <th className="px-4 py-3 text-left">创建时间</th>
              <th className="px-4 py-3 text-center">操作</th>
            </tr>
          </thead>
          <tbody className="divide-y">
            {users.length === 0 ? (
              <tr>
                <td colSpan={5} className="px-4 py-8 text-center text-gray-500">
                  暂无用户数据,请添加用户
                </td>
              </tr>
            ) : (
              users.map((user: any) => (
                <tr key={user.id}>
                  <td className="px-4 py-3">{user.id}</td>
                  <td className="px-4 py-3">{user.name}</td>
                  <td className="px-4 py-3">{user.email}</td>
                  <td className="px-4 py-3">{user.create_time}</td>
                  <td className="px-4 py-3 flex justify-center gap-2">
                    {/* 修改用户表单 */}
                    <form action={updateUser} className="inline">
                      <input type="hidden" name="id" value={user.id} />
                      <input
                        type="text"
                        name="name"
                        placeholder="新姓名"
                        className="w-24 px-2 py-1 border rounded"
                        required
                      />
                      <button
                        type="submit"
                        className="ml-1 px-3 py-1 bg-green-500 text-white rounded hover:bg-green-600"
                      >
                        修改
                      </button>
                    </form>

                    {/* 删除用户表单 */}
                    <form action={deleteUser.bind(null, user.id)} className="inline">
                      <button
                        type="submit"
                        className="px-3 py-1 bg-red-500 text-white rounded hover:bg-red-600"
                      >
                        删除
                      </button>
                    </form>
                  </td>
                </tr>
              ))
            )}
          </tbody>
        </table>
      </div>
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99

# 步骤5:运行效果

保存所有代码后,访问 http://localhost:3000/users,你即可实现:

  • 表单提交添加用户,直接写入数据库;
  • 实时展示数据库中的用户列表;
  • 一键修改用户姓名,更新数据库;
  • 一键删除用户,同步数据库;
  • 所有操作无刷新、无需写任何API接口、数据库逻辑100%安全,不会暴露给前端。

# 七、Next.js 企业级开发最佳实践(新手照做不踩坑)

# 7.1 渲染模式选择最佳实践

  1. 能静态就静态:官网、博客、文档等不常更新的页面,优先使用SSG静态生成,零服务器压力、访问速度最快;
  2. 动态内容加缓存:商品列表、新闻等需要定期更新的页面,使用ISR增量静态更新,设置 revalidate = 60(缓存60秒),既保证数据新鲜度,又避免频繁查询数据库;
  3. 仅实时场景用纯SSR:只有后台实时数据、个人中心等强个性化、强实时性的页面,才使用 cache: 'no-store' 强制不缓存,避免滥用导致服务器压力过大;
  4. 客户端组件最小化:只有需要用户交互(按钮点击、输入框、状态管理)的组件,才加 'use client' 标记,其余全部用服务端组件,保证性能和安全。

# 7.2 安全开发最佳实践

  1. 永远不要在客户端组件中写数据库连接、密钥等敏感信息,所有敏感操作必须放在Server Actions或服务端组件中;
  2. 环境变量规范:只有需要暴露给前端的变量,才以 NEXT_PUBLIC_ 开头,数据库密码、API密钥等敏感变量,绝对不能加此前缀;
  3. Server Actions 必须做参数校验:所有用户提交的数据,必须做格式校验、权限校验,不能直接写入数据库;
  4. 禁止在客户端组件中内联Server Actions,客户端组件必须导入单独文件的Server Actions,避免代码泄露。

# 7.3 性能优化最佳实践

  1. 使用内置的Image组件:所有图片都用 next/image,自动实现压缩、懒加载、自适应,避免图片导致的性能问题;
  2. 使用内置的Font组件:用 next/font 加载字体,避免字体闪烁、加载慢的问题;
  3. 路由预加载:用 next/link 做页面跳转,Next.js会自动预加载目标页面,实现秒开跳转;
  4. 避免大体积客户端组件:大的交互组件,使用动态导入 dynamic(() => import('./xxx')),实现按需加载,减少首屏JS体积。

# 7.4 部署最佳实践

  1. 优先选择Vercel部署:Next.js官方出品,一键部署、零配置、自动扩容,免费额度足够个人项目和小型企业项目使用;
  2. 自有服务器部署:可使用Docker打包部署,或执行 npm run build 打包后,用 npm run start 启动生产服务;
  3. 生产环境必须关闭开发模式,避免源码泄露和性能问题。

# 八、Next.js 适用场景 & 不适用场景

# 8.1 强烈推荐使用的场景

  1. 企业官网、品牌官网、营销落地页:SEO友好、首屏秒开、部署简单,完美匹配需求;
  2. 博客、文档站、内容门户、新闻站点:SSG+ISR组合,既保证访问速度,又能轻松更新内容;
  3. 电子商务平台、商城站点:SSR+ISR兼顾SEO和实时价格/库存更新,Server Actions安全处理订单逻辑;
  4. SaaS应用、后台管理系统、仪表盘:全栈一体化开发,一套代码搞定页面和后端逻辑,开发效率翻倍;
  5. AI应用、工具类网站:服务端安全调用AI API,避免密钥泄露,同时保证前端交互体验;
  6. 移动端优先的Web应用、PWA:内置性能优化,弱网环境体验远超传统SPA。

# 8.2 不推荐使用的场景

  1. 纯静态无动态内容的单页:比如只有1-2个页面的纯静态介绍页,可选择更轻量的Astro、VitePress;
  2. 已有成熟超大型微服务后端体系:不建议用Next.js替代后端,可作为前端渲染层,调用现有微服务接口;
  3. 非React技术栈项目:Next.js基于React,Vue/Angular技术栈建议选择对应框架(Nuxt、Analog)。

# 九、总结

# 核心总结

Next.js 彻底重构了现代Web开发的流程,它不是简单的React框架,而是一套完整的全栈开发解决方案:

  • 它解决了传统前后端分离的SEO、首屏性能、开发效率痛点;
  • 它保留了现代前端的丝滑交互体验,同时补齐了服务端能力;
  • 它让前端开发者无需学习额外的后端语言,只用React语法,就能开发完整的全栈应用;
  • 它的企业级生态和性能优化能力,让新手也能写出符合生产标准的Web项目。