Next.js 13+ 核心开发指南:从Vue迁移的最佳实践
- 路由系统:基于文件系统的App Router
- 目录结构即路由(app/about/page.jsx → /about)
- 动态路由使用[slug]文件夹命名
- 布局系统通过layout.jsx实现共享UI(类似Vue的App.vue)
- 数据获取革命
- 服务端组件直接使用async/await获取数据
- 无需useEffect/onMounted,数据预渲染提升SEO
- 自动处理Loading状态(loading.jsx)和缓存控制
- Server Actions新范式
- 表单提交无需API路由,直接定义服务端函数
- 支持渐进增强,即使JS禁用也能工作
- 内置revalidatePath实现数据更新后自动刷新
- 关键差异点
- 相比Vue的响应式数据流,Next.js采用服务端优先渲染
- 交互逻辑需通过"use client"标记的客户端组件处理
- 路由导航使用Next.js专属的<Link>和useRouter
本文完整呈现了从Vue转向Next.js的核心概念转变和技术栈差异,帮助开发者快速掌握现代React全栈开发模式。

创建项目
运行 npx create-next-app@latest。
在安装选项中,务必选择:
App Router: Yes (这是目前的标准)
Tailwind CSS: Yes (React 圈子最推荐的样式方案)
尝试在 app 下创建一个 about 文件夹,并写一个 page.jsx。
用 Next.js 优雅地获取服务器数据(直接写 async/await),还是想先弄清楚如何在 Next.js 里处理 CSS 样式?
路由系统
Next.js 的路由系统是它最核心的特性之一。作为 Vue 开发者,你可能习惯了在 router/index.js 中配置一个巨大的路由表,但在 Next.js 中,"目录结构即路由"。
Next.js 的路由系统是它最核心的特性之一。作为 Vue 开发者,你可能习惯了在 router/index.js 中配置一个巨大的路由表,但在 Next.js 中,"目录结构即路由"。
目前 Next.js 推荐使用 App Router(即 app/ 目录),它是基于文件系统的路由系统。
1. 基础路由 (Basic Routes)
在 app 文件夹 下,文件夹的名字决定了 URL 路径,而该文件夹下的 page.jsx 文件则是该路径渲染的 UI 内容。
根路由:app/page.jsx ➡️ /
嵌套路由:app/dashboard/page.jsx ➡️ /dashboard
深层嵌套:app/dashboard/settings/page.jsx ➡️ /dashboard/settings
注意 :文件夹里可以放其他文件(如 utils.js 或 Button.jsx),但只有名为 page.jsx 的文件才能被公开访问。这和 Vue 组件文件夹的组织方式很像。
2. 动态路由 (Dynamic Routes)
当你需要根据 ID 或 Slug 加载页面时(类似于 Vue 的 /user/:id),你可以使用 方括号 命名文件夹。
and [slug]]
目录结构:app/blog/[id]/page.jsx
对应 URL:/blog/123 或 /blog/hello-world
如何获取参数:
Next.js 会自动将参数注入到 page 组件的 params 属性中。
// app/blog/[id]/page.jsx
export default function BlogPost({ params }) {
// 假设访问 /blog/123,params.id 就是 "123"
return <h1>正在阅读文章:{params.id}</h1>;
}
3. 布局系统 (Layouts & Templates)
这是 Next.js 路由最强大的地方:持久化布局。
根布局 (Root Layout)
app/layout.jsx 是必须的。它相当于 Vue 的 App.vue,包含了 <html> 和 <body> 标签。它在整个应用中是持久化的,页面切换时不会重新渲染。
嵌套布局 (Nested Layouts)
如果你想让 /dashboard 下的所有页面都有一个侧边栏,但 /login 页面没有,你可以在 app/dashboard/ 下创建一个私有的 layout.jsx
javascript
// app/dashboard/layout.jsx
export default function DashboardLayout({ children }) {
return (
<div className="flex">
<aside>侧边栏</aside>
<main>{children}</main> {/* 这里的 children 就是 dashboard 目录下的各种 page */}
</div>
);
}
4. 特殊文件 (Special Files)
除了 page.jsx 和 layout.jsx,Next.js 路由系统还预定义了一些特殊文件,帮你处理各种边界情况:
文件名 作用 对应 Vue 概念
loading.jsx 定义该路径下的加载中状态(自动使用 React Suspense) 自定义 Loading 组件
error.jsx 定义该路径下的错误处理界面(捕获运行时错误) Error Boundary
not-found.jsx 当该路径下发生 404 时显示的页面 404 路由
例如,如果你在 app/dashboard 下放了一个 loading.jsx,当你从首页点击进入仪表盘时,Next.js 会自动在数据准备好之前显示这个 loading 内容,无需你手动控制 isLoading 状态。
|---------------------|-----------------------------------|----------------|
| 文件名 | 作用 | 对应 Vue 概念 |
| loading.jsx | 定义该路径下的加载中状态(自动使用 React Suspense) | 自定义 Loading 组件 |
| error.jsx | 定义该路径下的错误处理界面(捕获运行时错误) | Error Boundary |
| not-found.jsx | 当该路径下发生 404 时显示的页面 | 404 路由 |
在 app/dashboard 下放了一个 loading.jsx,当你从首页点击进入仪表盘时,Next.js 会自动 在数据准备好之前显示这个 loading 内容,无需你手动控制 isLoading 状态。
5. 导航 (Navigation)
① 组件导航:<Link>
不要使用原生的 <a> 标签,因为它会引起页面刷新。使用 Next.js 的 <Link> 组件,它支持预获取 (Prefetching),让跳转像秒开一样
import Link from 'next/link'
<Link href="/dashboard">去后台</Link>
② 编程式导航:useRouter
如果你要在逻辑中跳转(比如点击登录按钮后),使用 useRouter
重要提示 :在 App Router 中,必须从 'next/navigation' 导入,而不是 'next/router'。
javascript
'use client'; // 记住:有交互逻辑的组件要加这一行
import { useRouter } from 'next/navigation';
function LoginPage() {
const router = useRouter();
return (
<button onClick={() => router.push('/home')}>登录</button>
);
}
总结:Next.js 路由的心智模型
- 文件夹 = URL 路径。
- page.jsx = 页面内容。
- layout.jsx = 共享的外壳(不会随页面切换而刷新)。
- [] = 动态参数。
如何直接在"服务器组件"里用 async/await 拿数据?
在 Vue3 或纯 React 中,拿数据通常需要:ref/state + onMounted/useEffect + axios/fetch + 处理 loading 状态。
在 Next.js 的 服务端组件 (Server Components) 中,你可以像写后端代码一样,直接把组件写成 async 函数,然后用 await 拿数据。
------------------------------------------------
1. 基础用法:组件即数据请求器
在 app 目录下的文件默认都是服务端组件。你不需要任何 Hook,直接在函数体里 await 即可。
javascript
// app/users/page.jsx
// 1. 将组件定义为 async 函数
export default async function UsersPage() {
// 2. 直接请求数据(这是在服务器上运行的,不会暴露 API Key 给客户端)
const res = await fetch('https://api.example.com/users');
const users = await res.json();
// 3. 直接渲染数据
return (
<main>
<h1>用户列表</h1>
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</main>
);
}
2. 为什么这样做很"香"?
没有"水合"抖动SEO友好 :数据在 HTML 发送到浏览器之前就已经填好了,用户看到的就是最终内容,SEO 极佳。
直接访问数据库 :因为代码在服务器运行,你可以直接在组件里写数据库查询(如 db.query()),连 API 接口都可以省了。
安全性 :敏感的 Token 或秘钥只留在服务器,不会传给浏览器。
减小包体积:用于处理数据的第三方库(比如 moment.js 或 lodash)不会被下载到用户的浏览器里。
3. 如何处理 Loading 状态?
你可能会问:"如果是异步请求,页面加载时不是会白屏吗?"
Next.js 使用了 Streaming(流式传输) 。你只需要在同级目录下创建一个 loading.jsx。
javascript
// app/users/loading.jsx
export default function Loading() {
return <p>正在加载数据,请稍候...</p>;
}
当 page.jsx 在请求数据时,Next.js 会自动先给用户展示 loading.jsx 的内容,等数据请求完了再自动替换成 page.jsx。你不需要写任何判断 if (isLoading) 的逻辑。
4. 数据缓存与更新 (Revalidation)
Next.js 的 fetch 是加强版的。它默认会缓存数据。如果你希望数据是动态的,可以设置:
javascript
// 每 60 秒刷新一次缓存 (类似 ISR)
const res = await fetch('...', { next: { revalidate: 60 } });
// 或者是完全不缓存,每次请求都重新获取 (类似传统的 SSR)
const res = await fetch('...', { cache: 'no-store' });
5. 注意事项 (Vue 开发者易错点)
不能在 async 组件里用 Hook:如果你把组件写成了 async,你就不能在里面用 useState 或 useEffect。如果需要交互,请将交互部分抽离成一个带 'use client' 的子组件。
只能在服务端组件用:async 组件这种写法不适用于 'use client' 的客户端组件。客户端组件拿数据依然建议用传统的 useEffect 或更好的 SWR / React Query。
总结:
- Vue 思路:组件挂载 -> 发送请求 -> 更新响应式变量 -> 重新渲染。
- Next.js 思路:服务器请求数据 -> 渲染完整 HTML -> 发送给浏览器。
Server Actions
写"数据(比如提交表单、修改数据库)
在传统的 React/Vue 开发中,你需要写一个 API 接口,然后在前端用 axios.post 去调用。在 Next.js 中,我们有更优雅的选择:Server Actions。
1. 什么是 Server Actions?
Server Actions 允许你直接在组件里定义一个异步函数,并将其标记为 "use server"。当你调用这个函数时,Next.js 会自动帮你完成所有的网络请求封装。
心智模型:你就像在调用一个普通的本地函数,但它实际上是在服务器上执行的。
2. 基础用法:提交表单
这是最经典的用法。你不需要 useState 来绑定每一个输入框,直接利用 HTML 原生的 formData。
javascript
// app/posts/create/page.jsx
export default function CreatePost() {
// 1. 定义一个 Server Action
async function createPost(formData) {
'use server'; // 关键:告诉 Next.js 这是要在服务端运行的代码
const title = formData.get('title');
const content = formData.get('content');
// 2. 这里可以直接操作数据库(比如用 Prisma 或 SQL)
console.log(`正在保存文章:${title}`);
// 3. 处理完后,可以跳转或刷新页面
}
return (
<form action={createPost}>
<input name="title" placeholder="标题" />
<textarea name="content" placeholder="内容" />
<button type="submit">发布文章</button>
</form>
);
}
3. 核心优势:为什么不用传统的 API 请求?
类型安全:如果你使用 TypeScript,Server Actions 可以完美共享类型定义。
减少代码量:你不需要写 API Route,不需要处理 fetch('/api/...') 的繁琐过程。
渐进增强:即使用户的浏览器禁用了 JavaScript,这种基于 form action 的提交依然有效(这是 Next.js 追求的极致体验)
4. 修改数据后的"页面刷新" (Revalidation)
在 Vue 中,你修改了数据后,通常需要手动重新请求一次列表。在 Next.js 中,你只需要一行代码:revalidatePath
javascript
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
async function createPost(formData) {
'use server';
// ... 保存数据的逻辑 ...
// 告诉 Next.js:"/posts" 路径下的缓存过期了,请重新获取数据
revalidatePath('/posts');
// 跳转回列表页
redirect('/posts');
}
5. 处理 Pending 状态 (正在提交...)
既然是异步操作,用户点击按钮后总需要一个反馈。React 提供了新的 Hook useFormStatus 来处理这个。
注意 :因为它涉及交互,这个 Hook 必须在 'use client' 组件中使用。
javascript
'use client';
import { useFormStatus } from 'react-dom';
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button disabled={pending}>
{pending ? '正在发布...' : '发布文章'}
</button>
);
}
总结:
目前学习的React/Next.js 技能树
- 基础 React:JSX, Props, State, Hooks。
- Next.js 路由:文件即路由,嵌套布局。
- 数据读取 :服务端组件直接
await fetch。 - 数据写入:Server Actions 直接操作后端逻辑。