Next.js零基础实战:从Vite到全栈框架,掌握SSR、API与部署

发布时间:2026/10/1 11:58:40
Next.js零基础实战:从Vite到全栈框架,掌握SSR、API与部署 如果你最近在搜 React 项目的技术方案一定绕不开两个词Next.js 和 Vite。我先给结论这俩根本不是一个赛道的东西Vite 是构建工具Next.js 是基于 React 的全栈框架。真正让我决定从 Vite 转投 Next.js 的是它把前后端揉在了一起——不用单独开一个 Node 服务就能写接口、做 SSR、搞静态生成这对小团队和个人项目太省心了。这篇文章我打算以零基础视角把 Next.js 项目里最常碰到的路由、数据获取、API、鉴权、部署这些关键环节都过一遍顺便把我踩过的坑整理成速查表。适合正在学 React、准备从纯前端转全栈或者想快速搭一个可上线项目的人。1. 为什么从零开始学 Next.js 而不是只学 Vite1.1 先搞清楚 Next.js 到底是什么很多新手会误以为 Next.js 只是 Vite 的替代品其实不是。Vite 负责把源码编译成浏览器能跑的文件它是一个构建层而 Next.js 是一整套 React 框架它自己集成了编译、打包、路由、数据获取、API 路由和图片优化甚至还能选择静态导出。我通常用一个比喻Vite 是一辆性能很好的工具车能帮你把原材料快速运到目的地Next.js 则是一辆自带厨房的移动餐车你不仅能把东西运过去还能直接在车上完成加工和出餐。Next.js 的“出餐”能力就是服务端渲染SSR、静态生成SSG、增量静态再生成ISR和 API Routes。这些能力让一套代码既能跑在浏览器里又能跑在服务器上对外提供接口和页面。如果你只是做一个纯前端单页应用Vite 完全够用而且开发体验极快。但如果你想做项目比如一个带登录、带后台、带数据管理的完整系统用 Vite 你还得额外接 Express、Koa 或者服务端框架要处理跨域、接口鉴权、SEO 等等。Next.js 开箱就把这些环节打包好了从零学它反而是更省力的一条路。1.2 从 Vite 迁移过来的真实感受我之前用 Vite 写过一个后台项目结构是 Vite 前端 Express 服务端。前端的页面要请求后端接口开发时得开两个端口再配一个 Vite 代理解决跨域。生产环境更麻烦我需要把前端打包后的静态文件交给 Express 托管还要额外维护一份路由表避免前端路由刷新后 404。这套流程不是说不行但它把本该项目经理思考的问题拆给了开发者。换成 Next.js 之后我的感受非常明显页面和接口放在同一个项目里开发时前后端共享 host 和 port页面组件可以直接在服务端取数据不存在跨域问题部署的时候一个next build就把前端静态资源和服务端逻辑都处理好了。刷新某个路由也不会 404因为 Next.js 的路由是文件式的每个页面都有对应的服务端入口。还有个让我很意外的点是Next.js 对页面性能的约束是内置的。它默认不加载没有必要的 JavaScript服务端组件天然把 JS 体积降下来。原来在 Vite 项目里一个页面哪怕只是展示文字也得先下载 React 运行时和整个页面 bundle 才能渲染。换成 Next.js 后静态内容直接在 HTML 里返回首屏速度提升非常明显。1.3 学习成本确实是“从零基础”开始吗说句实话任何一个框架都不可能是真正的零基础。Next.js 建立在 React、JavaScript、HTTP 这些基础之上。但你不需要先完整学一遍 React 再学 Next.js完全可以边做边补。我的建议是至少理解 JavaScript 的基本语法、函数、数组、对象操作知道 React 里的组件、Props、State、事件处理是什么就可以上手 Next.js。需要注意一点现在 Next.js 官方默认推荐 App Router也就是app目录这种写法。网上很多老教程还在讲 Pages Router也就是pages目录和getServerSideProps。如果你是新项目我建议直接从 App Router 开始别再学 Pages Router 了。为什么因为 Next.js 15 已经把 App Router 当作默认方案新的文档、社区插件、模板都以它为主。学旧方案虽然也能跑但之后还是要切换等于练一套要淘汰的武功。App Router 的入门曲线其实比 Pages Router 更直白。文件放哪路由就是哪数据获取直接在组件里用async函数不用再记一堆生命周期方法。很多东西看起来反直觉但只要理解了服务端组件和客户端组件的边界后面的路就是通畅的。2. 从零搭建第一个 Next.js 项目环境、目录与核心概念2.1 环境准备与安装命令我在本地用的 Node.js 20 LTS包管理器用的是 pnpm。Node 版本不能太低最好保持 18.18 以上否则某些新特性会报错。安装项目很简单打开终端执行pnpm create next-applatest my-app运行过程中会问你几个问题TypeScript 要不要、ESLint 要不要、Tailwind CSS 要不要、App Router 还是 Pages Router、是否用 Turbopack 做开发构建。我的选择是TypeScript 要ESLint 要Tailwind 看项目需要路由用 App RouterTurbopack 可以先选上试试。TypeScript 比 JavaScript 多点类型约束但能帮你少踩非常多的低级错尤其服务端组件和客户端组件传参的时候类型提示能直接告诉你哪里越界了。创建完成后进入目录启动开发服务cd my-app pnpm dev打开http://localhost:3000你会看到一个默认首页。这时候项目目录里最核心的几个文件是app/layout.tsx全局布局整个应用的根骨架app/page.tsx首页页面组件public/静态资源目录next.config.tsNext.js 配置文件middleware.ts中间件后面做鉴权时用在 Next.js 里目录结构就是路由结构。app/about/page.tsx对应/about路由app/blog/[id]/page.tsx对应/blog/1、/blog/2这种动态路由。第一次用的时候会觉得这种映射非常朴素但它确实省掉了一张路由表。2.2 App Router、Layout 与三个特殊文件App Router 的核心规则只有一句话page.tsx里面导出的 React 组件就是 URL 对应的页面内容。比如我在app/demo/page.tsx里写export default function DemoPage() { return div这是演示页面/div; }访问/demo就能看到内容。如果是嵌套结构app/blog/[id]/page.tsx就自动匹配/blog/任意值你可以通过组件的params参数拿到动态值export default async function PostPage({ params, }: { params: Promise{ id: string }; }) { const { id } await params; return div文章 ID{id}/div; }在 Next.js 15 里params是异步的要用await params才能取到值。这是我一开始经常漏的地方直接params.id会拿不到。App Router 里还有几个特殊文件建议一开始就理解layout.tsx是布局文件可以让多个页面共用头部、侧边栏、页脚这种公共部分。根布局app/layout.tsx是必须存在的整个应用的外壳就是它。loading.tsx是路由级别加载状态当页面在服务端等待数据时Next.js 会自动显示这个组件体验会比白屏好很多。error.tsx是错误边界页面抛出异常时它会接收error对象和reset函数用来展示出错信息和重试按钮。not-found.tsx是 404 页面当路由不匹配或者组件里调用notFound()时会渲染它。这几个文件我一开始没有主动去建等到用户反馈页面加载时一片空白、报错时直接崩掉才往回补其实最好在项目启动第一天就建好成本很低。2.3 服务端组件与客户端组件什么时候用 use clientApp Router 里最重要的概念就是服务端组件Server Component和客户端组件Client Component。默认情况下page.tsx和它的子组件都是服务端组件它们只会在服务器上执行不会把代码打包到浏览器里。这带来两个好处一是首屏 HTML 直接在服务器生成加载快二是数据库密码、API Key 这类敏感信息不会暴露给客户端。但如果你的组件里需要 React 状态、事件监听、浏览器 API那就必须加use client。这行指令不是“这是一个客户端网页”的意思而是“这个组件允许在浏览器里运行”。一个常见的组合方式是外层页面用服务端组件取数据取完之后传给一个带交互的子组件子组件负责点击、输入、折叠这类操作。举个例子假设我有一个文章列表页面服务端代码长这样// app/posts/page.tsx import { fetchPosts } from /lib/api; import PostList from /components/PostList; export default async function PostsPage() { const posts await fetchPosts(); return PostList posts{posts} /; }而PostList是客户端组件因为它需要记录用户点选了哪篇文章use client; import { useState } from react; export default function PostList({ posts }: { posts: { id: number; title: string }[] }) { const [selected, setSelected] useStatenumber | null(null); return ( ul {posts.map((post) ( li key{post.id} onClick{() setSelected(post.id)} {post.title} {selected post.id ? ✓ : } /li ))} /ul ); }这套模式我用了很久之后体会很深大部分数据加载和格式化放在服务端组件里页面点击、表单输入、弹窗这类逻辑才放到客户端组件里。代码很干净而且客户端组件只占整个 bundle 的小部分体积控制得非常舒服。2.4 数据获取实操SSR、SSG、ISR 不再迷糊在 App Router 里数据获取的方式非常直观组件本身可以是一个async function你直接在里面await fetch数据就行。但是不同场景下数据的更新策略是不同的我用一张表帮你理清场景方式数据更新时间适合场景静态生成 SSGfetch(url, { cache: force-cache })或不依赖请求数据的默认静态构建构建时博客、产品页、不频繁变动的文档服务端渲染 SSRfetch(url, { cache: no-store })或组件内export const dynamic force-dynamic每次请求需要实时数据的仪表盘、用户中心、评论增量静态生成 ISRexport const revalidate 60;最多每 60 秒重新请求一次价格页面、榜单、新闻列表我记得从 Pages Router 转过来的同学最容易犯的错误是找getStaticProps但在 App Router 里没有这个方法。直接这样写就可以了// app/ssr/page.tsx export default async function SsrPage() { const res await fetch(http://api.example.com/data, { cache: no-store }); const data await res.json(); return div{JSON.stringify(data)}/div; }如果要 ISR在页面文件里加一行export const revalidate 60;这意味该页面最多 60 秒重新生成一次访问速度接近静态页面但又不会一直显示旧数据。我自己的经验是优先考虑静态生成某些页面确实要求实时数据再改成动态ISR 是我最常用的折中方案大多数列表和详情页都用它。3. 项目实战从零做一个带登录和权限的后台系统3.1 第一周搞定页面骨架和列表页如果只是停留在 hello world学框架就没意义了。我建议第一个项目做一个小后台管理系统包含登录页、首页、用户列表和文章管理。这个项目很经典因为它能覆盖 Next.js 的绝大多数核心功能。第一周先把页面骨架搭出来。创建这些文件app/layout.tsx里放侧边栏和顶部栏app/dashboard/page.tsx作为后台首页app/dashboard/users/page.tsx作为用户列表app/login/page.tsx作为登录页布局文件里我通常会把侧边栏也作为服务端组件直接渲染不把它做成客户端交互。如果后面想折叠侧边栏那只需要把折叠按钮和状态抽取成一个客户端小组件传入侧边栏组件里。这个思路我反复强调不要整个页面都加上use client一定只给需要交互的部分加。用户列表页可以先做成静态的假数据把数据形态和 UI 固定下来。我先定义好 TypeScript 类型type User { id: number; name: string; email: string; role: string; };然后用一个数组模拟数据渲染出来。目的不是写死代码而是先把 TypeScript 类型、组件结构、Tailwind 样式都调通后面接真实接口时只需改数据获取层UI 完全不用动。3.2 第二周API Routes 与数据库接入Next.js 自带 API Routes在app/api/目录下建route.ts文件就能对外提供接口。比如我要实现一个 todo 接口创建app/api/todos/route.tsimport { NextResponse } from next/server; export async function GET() { const todos [ { id: 1, title: 学习 Next.js, done: false }, { id: 2, title: 搭建后台系统, done: false }, ]; return NextResponse.json(todos); }访问/api/todos就能拿到 JSON而且和前端页面是同一个域名、同一个端口完全没有跨域问题。这在以前 Vite Express 的开发方式里是不可想象的。更关键的是API Routes 运行在服务器上可以安全地操作数据库。我第二个项目里接入了 Prisma 和 SQLite因为零基础项目最怕配数据库服务。SQLite 就是一个文件不需要额外安装数据库软件适合学习。Prisma 的作用是提供类型安全的数据库操作你定义好模型之后它会自动生成对应的 TypeScript 类型。常见的route.ts写法是import { NextResponse } from next/server; import { prisma } from /lib/prisma; export async function GET() { const users await prisma.user.findMany(); return NextResponse.json(users); } export async function POST(request: Request) { const body await request.json(); const user await prisma.user.create({ data: { name: body.name, email: body.email }, }); return NextResponse.json(user, { status: 201 }); }这里的/lib/prisma是指向lib/prisma.ts的路径别名。/开头代表项目根目录的src或直接根目录具体看tsconfig.json里的配置。这样页面组件、路由处理函数、后台逻辑都可以通过 Prisma 直接操作数据库数据模型一目了然。3.3 第三周登录态与中间件鉴权后台系统绝对绕不开登录和权限。我以前做前端的时候觉得鉴权是后端的事自己写服务端时才明白这层逻辑通常放在“入口处”。Next.js 的中间件就是一个很好的入口。最简单的方案是基于 cookie 的登录态。用户提交用户名密码成功后后端在响应里设置一个httpOnly的 cookie里面放一个签过名的 token。浏览器后续每次请求都会自动带上 cookie后端中间件只检查 cookie 是否存在就能判断是否登录。中间件代码可以这样写import { NextRequest, NextResponse } from next/server; export function middleware(request: NextRequest) { const token request.cookies.get(token)?.value; const isLoginPage request.nextUrl.pathname.startsWith(/login); const isProtectedPage request.nextUrl.pathname.startsWith(/dashboard); if (!token isProtectedPage) { return NextResponse.redirect(new URL(/login, request.url)); } if (token isLoginPage) { return NextResponse.redirect(new URL(/dashboard, request.url)); } return NextResponse.next(); } export const config { matcher: [/dashboard/:path*, /login], };这段代码的逻辑很简单访问/dashboard但没有 token 就跳转登录已经登录了还去/login就跳回后台。matcher 指定了需要执行的路径范围减少全站无谓的中间件开销。这只是最基础的鉴权生产环境还需要校验 token 是否过期、用户角色是否有权限。不过这个骨架已经能让你理解登录流程的全貌。如果你不想自己造轮子也可以引入 NextAuth但我的建议是先自己实现一次能更好地理解 session 和 cookie 的关系。3.4 部署选平台还是自己买服务器Next.js 项目做完之后部署是一件很有成就感的事。最常见的部署方式是 Vercel它会自动识别项目、执行next build、管理环境变量和域名。你只需要把代码推到 Git 仓库然后在 Vercel 里导入仓库就行。这种方式最适合个人项目和原型。如果项目要求部署到自己的服务器上可以用 Docker 或者直接用 Node.js 的next start。先本地执行pnpm build pnpm start生产服务器上通常需要做反向代理比如 Nginx 把 80/443 端口的请求转发到 Next.js 所在的 3000 端口。这个模式很成熟适合需要自己维护环境的团队。还有一点要特别注意如果你的项目里有 API Routes不能用静态导出模式。静态导出output: export会生成纯静态文件它适合博客、落地页但 API Routes、动态路由、服务端渲染都没有了。后台管理系统我一律建议部署到能运行 Node.js 的平台或服务器上。4. 高频踩坑与排查速查表4.1 页面怎么刷新都是旧的缓存和 ISR 生效了吗我做第一个 ISR 页面时明明设了revalidate 60但刷新之后内容还是旧数据。排查到最后发现问题不在于框架而在于我第一次构建后一直用pnpm start跑它确实会按 60 秒间隔重新验证但浏览器端也可能缓存了响应。如果你要测试 ISR最好在浏览器无痕窗口里看或者用 curl 请求并带上?x时间戳之类的参数避免拿到缓存。另一点App Router 里fetch默认的缓存策略在不同版本有变化。Next.js 15 里fetch默认不再是强制缓存如果你希望一个接口在构建时预取需要显式设置cache: force-cache如果希望不缓存就设置cache: no-store。我自己的实践是宁可写清楚缓存策略也不要依赖框架默认值这样升级框架时不会突然变样。4.2 水合错误useEffect 里读 window 为什么崩溃刚接触服务端组件时最容易碰到的错误就是水合不匹配。错误信息大概是Hydration failed because the server rendered HTML didnt match the client.根本原因是服务端生成 HTML 的时候没有window、没有localStorage、没有用户浏览器专属信息而客户端第一次渲染时却读了这些值两边 HTML 对不上。比如这段代码export default function ThemeToggle() { const [theme, setTheme] useState(localStorage.getItem(theme) ?? light); return div{theme}/div; }服务端渲染时localStorage不存在直接抛错。即使你把它放进useState的初始值里也不行因为服务端也会执行这行代码。正确的做法是让初始渲染在服务端和客户端保持一致然后在useEffect里再根据浏览器信息更新状态use client; import { useEffect, useState } from react; export default function ThemeToggle() { const [theme, setTheme] useState(light); useEffect(() { setTheme(localStorage.getItem(theme) ?? light); }, []); return div{theme}/div; }useEffect只在客户端执行所以不会破坏服务端渲染的一致性。这是所有涉及浏览器全局对象的通用解法。4.3 环境变量的服务端与客户端陷阱Next.js 的环境变量分为两类。以NEXT_PUBLIC_开头的变量会暴露在浏览器端适合放前端公开配置比如 API 地址。没有这个前缀的变量只在 Node.js 服务端可用适合放数据库连接串、密钥、第三方服务的 token。我见过同事把数据库地址写成NEXT_PUBLIC_DATABASE_URL结果数据库信息直接被打进前端 bundle 里。这非常危险。正确的做法是服务器环境变量不加大前缀在服务端组件或 API Routes 里使用客户端组件里永远不要读这类变量。如果要在服务端代码里读取环境变量直接使用process.env即可const connectionString process.env.DATABASE_URL;但如果你在客户端组件里写process.env.DATABASE_URLNext.js 构建时会直接把它替换成undefined并且在运行时不一定报错反而更难排查。这种问题最好通过预先检查代码或者部署平台的日志系统来发现。4.4 常见构建错误快速对照表我整理了一份自己碰到过的错误速查表先照着检查能省不少时间报错信息常见原因解决方法Module not found: Cant resolve /...tsconfig 的 paths 没配好检查tsconfig.json中baseUrl和paths确保/*指向正确的目录Hydration failed because server rendered HTML didnt match...在组件初始渲染时读取了浏览器 API改用useEffect或在组件挂载后再更新状态params取不到值Next.js 15 中动态路由参数变为异步使用const { id } await params;The resource was preloaded but not used图片或脚本预加载顺序问题检查next/image的尺寸是否准确避免过度预加载TypeError: Cannot read properties of undefined (reading use client)导入路径错误或循环导入拆开循环依赖检查是否误把普通组件当服务端组件导入PrismaClient is not definedserverless 环境重复实例化客户端建立 Prisma 单例避免每个请求创建新实例Prisma 单例是件小事但影响很大。每次next build时如果多个请求同时初始化数据库客户端可能触发连接数暴涨。我的做法是在lib/prisma.ts里加一个全局缓存import { PrismaClient } from prisma/client; const globalForPrisma globalThis as unknown as { prisma?: PrismaClient }; export const prisma globalForPrisma.prisma ?? new PrismaClient(); if (process.env.NODE_ENV ! production) { globalForPrisma.prisma prisma; }这段代码保证在开发时只创建一个实例生产环境每次部署也复用同一个全局实例。5. Next.js 和 Vite 到底怎么选5.1 两者根本不是同一层的东西如果你想搜“nextjs 和 vite”到底哪个好首先要纠正一个认知它们是不同维度。Vite 是一个构建工具/开发服务器它负责把 JSX、TypeScript 编译成浏览器能识别的文件开发模式下提供极快的模块热更新。Next.js 虽然也有组件编译、热更新、打包但它更重要的是提供了“整个应用怎么组织、怎么获取数据、怎么处理服务端逻辑”的框架。你可以理解为 Vite 只管“把代码变成可运行”而 Next.js 要管“应用怎么跑、在哪儿跑、页面和接口怎么配合”。开发体验上两者目前都在向“快”靠拢。Vite 用 esbuild 预打包依赖启动非常快Next.js 15 默认用 Turbopack 做开发构建速度提升也很明显。但如果你需要的只是一个 CSR 单页应用没有 SEO 需求也没有接口统一放服务端的需求Vite 足够轻、足够省事。如果目标是做完整项目Next.js 的价值就体现出来了。5.2 选型判别的场景清单我可以根据自己的项目经验给出一个比较实用的选型清单项目类型推荐理由公司官网 / 内容站点 / 博客Next.js需要 SEOSSG/ISR 是天然优势后台管理系统Next.js页面和 API 放一起不用单独搭服务端纯营销落地页Vite 或 Next.js落地页简单两者都行如果需要快速预渲染建议 Next.js复杂图形编辑器 / 大量本地交互Vite这类应用几乎全是客户端逻辑服务端渲染意义不大组件库 / 工具库开发Vite直接导出库产物构建速度快配置简单全栈学习项目Next.js一条链路学完前端、后端、数据库、部署我的经验是后台系统和官网用 Next.js 非常顺手因为它能把页面逻辑、接口逻辑、鉴权逻辑都写在一个项目里类型也能共享。比如前端定义了一个User类型接口返回的数据也正好是这个类型不用两边各写一份。这种“端到端类型安全”的体验用 Vite 单独接服务端很难实现除非你再引入 monorepo 和类型包复杂度会增加不少。5.3 我的学习路线建议很多人纠结零基础到底先学 Vite 还是先学 Next.js。我的建议是如果你已经有 React 基础直接学 Next.js别绕路如果你连 React 都还没摸过可以先拿 Vite 跑一个极简的 React 项目写几个组件理解 Props、State、事件然后再跳到 Next.js不然你可能分不清框架报错到底是 React 的坑还是 Next.js 的坑。从 Vite 切到 Next.js 时不用急着学所有概念。你可以先搭一个 Next.js 项目把所有页面都当作静态组件写暂时不碰服务端组件和 API Routes。然后在此基础上加一两个客户端交互组件把use client的边界搞清楚。之后再慢慢接触数据获取、环境变量、中间件。这个渐进路线让我少了很多挫败感。如果你完全没有前端基础我建议顺序是先学 HTML/CSS/JavaScript 基础大概 2 周再学 React 核心比如组件、状态、副作用大概 2 周最后才开始 Next.js。不要一上来就搜“Next.js 教程”没有前面基础你会像看天书。6. 最后想分享的一点实战经验如果你问我学 Next.js 最值得花时间理解的是什么我会说是“服务端和客户端的边界”。很多人写代码时习惯把所有逻辑都放在页面上结果整个页面被迫变成客户端组件服务端渲染的优势就没了。我的习惯是先用服务端组件把数据拿到再把数据传给一个小范围的客户端组件只有实在需要浏览器 API 或状态时才加use client。这样代码跑起来更轻首屏渲染也更稳定。据我实际体验Next.js 的周边生态还在快速变化版本升级后一些默认行为会变。建议你遇到和网上教程对不上的现象时第一时间去看官方文档不要死磕博客里的旧写法。我用 Next.js 做了几个项目之后最大的体会是它确实降低了一个人做全栈项目的门槛但你对 HTTP、缓存、鉴权这些基础知识的理解决定了你用得顺不顺手。框架能帮你省掉大量重复工作但该懂的原理还是得懂。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询