现代全栈框架概览
前端与后端的边界正在消失
🎯 学习目标
学完本章,你将能够:
- 理解 SSR、SSG、ISR 等渲染策略的差异和适用场景
- 掌握 Next.js 的 Server Components 和数据获取模式
- 了解 Nuxt 3 的自动导入和组合式 API
- 了解 Remix 和 SvelteKit 的核心设计理念
- 根据项目需求选择合适的全栈框架
你有没有想过
为什么 Next.js 能在服务端查数据库,还能在客户端做交互?前后端代码为什么要分两个项目?
传统开发中,前端和后端是两个独立的项目、两套部署流程、两种类型定义。而现代全栈框架让它们融合在同一个项目里,共享类型、共享逻辑、一次部署。
这个比喻只是为了帮你建立直观感受:以前前后端像两家公司做生意,现在像一家公司的两个部门。当然,框架内部仍然有明确的服务端和客户端边界。
全栈演进历程
从模板渲染到全栈融合
| 阶段 | 代表技术 | 前后端关系 |
|---|---|---|
| 传统 MVC | Express + EJS | 后端渲染模板 |
| SPA 时代 | React + REST API | 前后端完全分离 |
| SSR 混合 | Next.js SSR | 服务端渲染 + 客户端水合 |
| 全栈融合 | Next.js App Router | 同一项目,共享类型 |
现代全栈框架的核心特征:同构渲染、文件系统路由、内置 API 支持、混合渲染策略。
渲染策略对比
选对策略,性能翻倍
| 策略 | 何时渲染 | 适用场景 |
|---|---|---|
| SSR | 每次请求时服务端渲染 | 动态内容、SEO 要求高 |
| SSG | 构建时生成静态 HTML | 博客、文档 |
| ISR | 构建时 + 后台按需更新 | 内容不频繁更新 |
| CSR | 浏览器端 JS 渲染 | 仪表盘、管理后台 |
| Streaming SSR | 分块流式传输 HTML | 大型页面、慢数据源 |
核心原则:尽量静态,按需动态。优先 SSG/ISR,只在需要实时数据时用 SSR。
渲染策略详解
五种策略的关键指标与取舍
| 策略 | TTFB | FCP | SEO | 服务器负载 | 典型场景 |
|---|---|---|---|---|---|
| SSG | 极低(CDN) | 极快 | 优 | 无 | 博客、营销页、文档站 |
| ISR | 低(CDN) | 快 | 优 | 低 | 商品列表、新闻资讯 |
| SSR | 较高 | 中等 | 优 | 高 | 个性化首页、搜索结果 |
| CSR | 高(等 JS) | 慢 | 差 | 无 | 管理后台、内部工具 |
| Streaming SSR | 低(分块) | 较快 | 优 | 中 | 电商首页、复杂仪表盘 |
TTFB(Time to First Byte):从请求发出到收到第一个字节的时间。FCP(First Contentful Paint):用户首次看到内容的时间。SSG 直接从 CDN 返回静态文件,所以两者都极低。CSR 必须等 JS 下载执行后才能渲染,FCP 最慢。Streaming SSR 把页面拆成多个 chunk 逐步发送,让用户更快看到内容骨架。
渲染时序
四种策略在时间轴上的区别

图:SSR / SSG / ISR / CSR 四种渲染策略的时序对比
【AI 生图提示词】请生成一张中文技术示意图,风格简洁、配色柔和、适合教学: - 画面主体:四条水平时间轴,从左到右分别代表 SSR、SSG、ISR、CSR 四种渲染策略的完整生命周期 - 每条时间轴的起点和终点标注关键事件: - SSR 时间轴:用户请求 → 服务端渲染 HTML → 返回给浏览器 → 页面可见(标注"每次请求都渲染") - SSG 时间轴:构建阶段 → 生成静态 HTML(标注"只渲染一次")→ 用户请求 → 直接返回 CDN → 页面可见 - ISR 时间轴:构建阶段 → 生成静态 HTML → 用户请求 → 返回缓存版本 → 后台检查更新 → 如有更新则重新生成(标注"定时刷新") - CSR 时间轴:用户请求 → 返回空 HTML 壳 → 下载 JS Bundle → 执行 JS → 数据请求 → 渲染完成(标注"浏览器端渲染") - 四条时间轴垂直排列,用不同颜色区分:SSR 用蓝色、SSG 用绿色、ISR 用橙色、CSR 用紫色 - 在右侧用虚线标注"TTFB"和"FCP"两个关键时间节点,横跨所有时间轴 - 配色建议:浅灰背景,每条时间轴用对应的浅色带,关键节点用深色圆点标注 - 画面比例:16:9 - 避免:过度装饰、3D 效果、过多文字堆砌 - 标注文字用中文,字号适中
Part 1: Next.js
目前最流行的 React 全栈框架,由 Vercel 维护,App Router 基于 React Server Components 架构。我们将学习以下核心能力:
- Server / Client Components——服务端组件直接查数据库,客户端组件处理交互
- 数据获取策略——fetch cache / revalidate / ISR / SSG / SSR 灵活切换
- 布局系统——layout.tsx / template.tsx 构建嵌套布局
- 中间件——在请求到达页面之前执行鉴权、重定向等逻辑
- API Routes——前后端一体,API 就在项目里
- 部署方式——Vercel 一键部署或 Self-host 自托管
Server Components
在服务端渲染,不发送 JS 到客户端
定义:Server Component(服务端组件)在服务器上执行,渲染结果以 HTML 形式发送到浏览器,不会打包任何 JS 到客户端。
白话:就像餐厅后厨直接把做好的菜端上桌,顾客不需要知道菜怎么做。浏览器收到的是纯 HTML,不需要下载额外的 JavaScript。
// app/users/page.tsx(默认就是 Server Component)
import { db } from '@/lib/db';
export default async function UsersPage() {
// 直接查数据库,不需要 API!
const users = await db.user.findMany({
select: { id: true, name: true, email: true },
});
return (
<div>
<h1>用户列表</h1>
<UserTable users={users} />
</div>
);
}
Server Component 可以直接访问数据库、文件系统和环境变量,但不能使用 useState、useEffect 等客户端 API。
Client Component
需要交互时加上 'use client'
定义:Client Component(客户端组件)通过 'use client' 声明,会在浏览器中运行,支持状态管理和事件处理。
白话:如果页面需要"响应用户操作"(比如搜索、点赞、弹窗),就得用客户端组件,因为浏览器才能监听鼠标和键盘。
// components/UserTable.tsx
'use client'; // 必须声明
import { useState } from 'react';
export function UserTable({ users }) {
const [search, setSearch] = useState('');
const filtered = users.filter(u =>
u.name.toLowerCase().includes(search.toLowerCase())
);
return (
<>
<input placeholder="搜索用户..."
value={search}
onChange={e => setSearch(e.target.value)} />
<table>
{filtered.map(user => (
<tr key={user.id}>
<td>{user.name}</td>
<td>{user.email}</td>
</tr>
))}
</table>
</>
);
}
Server vs Client
一张表搞清楚两者的区别
| 特性 | Server Component | Client Component |
|---|---|---|
| 获取数据 | 直接查数据库 | 需要通过 API |
| 使用 useState | 不可以 | 可以 |
| 使用 onClick | 不可以 | 可以 |
| 打包体积 | 不发送 JS | 发送 JS bundle |
| 访问后端资源 | 数据库、文件系统 | 不可以 |
尽量让组件保持为 Server Component,只在需要交互时才加 'use client'。
数据获取策略
fetch 扩展让缓存控制声明化
定义:Next.js 扩展了原生 fetch,添加了 cache 和 next.revalidate 选项,让你在数据请求层面直接控制渲染策略。
白话:以前缓存靠 Nginx 和 CDN 配置,现在直接写在代码里。告诉 Next.js "这个数据多久刷新一次"就够了。
// SSG:构建时获取,永不刷新(适合静态内容)
const res = await fetch('https://api.example.com/about', {
cache: 'force-cache',
});
// SSR:每次请求都重新获取(适合实时数据)
const res = await fetch('https://api.example.com/realtime', {
cache: 'no-store',
});
// ISR:每 60 秒后台重新验证(适合semi-static内容)
const res = await fetch('https://api.example.com/posts', {
next: { revalidate: 60 },
});
这三种模式覆盖了绝大多数场景。默认是 force-cache(SSG),如果你不指定缓存策略,Next.js 会在构建时获取数据并缓存。加 no-store 切换到 SSR,加 revalidate 切换到 ISR。
ISR 与按需重验证
静态页面的"自动刷新"机制
定义:ISR(Incremental Static Regeneration)在构建时生成静态页面,之后按设定间隔在后台重新生成。还可以用 revalidatePath 手动触发更新。
白话:像新闻 APP 的缓存策略——先给你看缓存内容,后台悄悄拉最新的,下次打开就是新的。
// app/posts/[id]/page.tsx
export const revalidate = 3600; // 每小时重新验证
export default async function PostPage({ params }) {
const post = await fetch(
`https://api.example.com/posts/${params.id}`,
{ next: { revalidate: 3600 } }
).then(r => r.json());
return <article><h1>{post.title}</h1><p>{post.content}</p></article>;
}
// 手动触发重验证(例如:后台编辑文章后调用)
// app/api/revalidate/route.ts
import { revalidatePath } from 'next/cache';
export async function POST(request) {
const { path } = await request.json();
revalidatePath(path);
return Response.json({ revalidated: true });
}
布局系统
layout 与 template 的区别
定义:layout.tsx 定义共享布局,切换子路由时不会重新渲染。template.tsx 也是共享布局,但每次导航都会重新挂载。
白话:layout 像网站的外框(导航栏+侧边栏),切页面时外框不变;template 像模板纸,每次翻页换一张新的。
// app/layout.tsx — 根布局(整个应用共享,不会重新渲染)
export default function RootLayout({ children }) {
return (
<html lang="zh">
<body>
<nav>网站导航</nav>
<main>{children}</main>
<footer>版权信息</footer>
</body>
</html>
);
}
// app/dashboard/layout.tsx — 嵌套布局
export default function DashboardLayout({ children }) {
return (
<div className="dashboard">
<Sidebar /> {/* 切换子路由时 Sidebar 保持不变 */}
<section>{children}</section>
</div>
);
}
布局可以嵌套:根布局包裹仪表盘布局,仪表盘布局再包裹具体页面。每层 layout 都有自己的 children 插槽。template.tsx 适合需要每次导航都触发 useEffect 或动画重置的场景。
中间件 Middleware
请求到达页面之前的第一道关卡
定义:Middleware 是运行在 Edge Runtime 中的函数,在每个请求到达路由处理程序之前执行。常用于鉴权、重定向、日志、A/B 测试。
白话:像大厦门口的保安——你要先进保安那关(鉴权),才能进电梯(路由)。保安不关心你进了哪个房间(页面逻辑),只管"让不让进"。
// middleware.ts(放在项目根目录或 src/ 下)
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
// 检查登录状态
const token = request.cookies.get('auth-token')?.value;
if (!token) {
// 未登录:重定向到登录页
const loginUrl = new URL('/login', request.url);
loginUrl.searchParams.set('from', request.nextUrl.pathname);
return NextResponse.redirect(loginUrl);
}
// 已登录:继续请求(可以在这里注入用户信息到 headers)
const response = NextResponse.next();
response.headers.set('x-user-id', decodeToken(token).userId);
return response;
}
// 配置:只对 /dashboard 下的路由生效
export const config = {
matcher: '/dashboard/:path*',
};
Middleware 运行在 Edge Runtime(轻量级 V8 环境),不是 Node.js。所以不能使用 fs、path 等 Node.js API,也不能直连数据库。它只适合做轻量级的请求拦截和重定向。
API Routes
前后端一体,API 就在项目里
// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
const CreateUserSchema = z.object({
name: z.string().min(1).max(50),
email: z.string().email(),
password: z.string().min(8),
});
export async function GET(request: NextRequest) {
const users = await db.user.findMany({
select: { id: true, name: true, email: true },
});
return NextResponse.json({ users });
}
export async function POST(request: NextRequest) {
const body = await request.json();
const data = CreateUserSchema.parse(body);
const user = await db.user.create({ data });
return NextResponse.json(user, { status: 201 });
}
部署方式
Vercel 一键部署或 Self-host
| 方式 | 适用场景 | 优势 | 劣势 |
|---|---|---|---|
| Vercel | 个人项目、初创团队 | 零配置、自动 CI/CD、Edge Functions | 免费额度有限、供应商绑定 |
| Docker 自托管 | 企业项目、合规要求 | 完全控制、无供应商绑定 | 需自己运维、配置复杂 |
| Node.js Server | 需要 SSR 的传统服务器 | 灵活、可接入现有基础设施 | 需要手动处理负载均衡 |
| Static Export | 纯静态站点 | 部署到任意静态托管 | 不支持 SSR/API Routes |
# Vercel 部署(推荐新手)
$ npx vercel
# Docker 自托管
$ docker build -t my-nextjs-app .
$ docker run -p 3000:3000 my-nextjs-app
# 静态导出(next.config.js 中设置 output: 'export')
$ npm run build # 输出到 out/ 目录
Part 2: Nuxt 3
Vue 生态的全栈框架,由 Nuxt 团队维护。它的核心卖点是自动导入和 Nitro 服务引擎。我们将学习:
- Auto-imports——组件、组合式函数自动导入,无需手写 import
- 文件路由——目录结构即路由,零配置
- useFetch / useAsyncData——声明式数据获取
- Server API / Middleware——服务端 API 和中间件
自动导入与路由
写文件就是在写路由和组件
定义:Nuxt 3 会自动扫描 components/、composables/、pages/ 目录,自动注册组件和路由,无需手动 import。
白话:在别的框架里你要写三件事——import 组件、注册路由、导出组件。在 Nuxt 里你只需要创建文件,剩下的全自动。
项目结构:
├── components/
│ └── UserCard.vue → 自动注册为 <UserCard />
├── composables/
│ └── useAuth.ts → 自动导入为 useAuth()
├── pages/
│ ├── index.vue → 路由 /
│ ├── users/
│ │ ├── index.vue → 路由 /users
│ │ └── [id].vue → 路由 /users/:id(动态路由)
│ └── about.vue → 路由 /about
文件路由的命名规则:[id].vue 对应动态参数 route.params.id,[...slug].vue 对应 catch-all 路由。Nuxt 会根据目录结构自动生成 Vue Router 配置。
Nuxt 数据获取
useFetch 自动导入,无需 import
定义:useFetch 是 Nuxt 提供的组合式函数,在服务端预取数据,在客户端自动缓存和去重。useAsyncData 是它的底层 API,可以包装任意异步操作。
白话:useFetch = 自动帮你发请求 + 自动处理加载状态 + SSR 时在服务端执行一次。你不用关心"这段代码跑在服务端还是客户端"。
<!-- pages/index.vue -->
<script setup lang="ts">
// useFetch 自动导入,无需 import
const { data: posts, pending, error, refresh } = await useFetch('/api/posts', {
key: 'homepage-posts', // 缓存 key(防重复请求)
transform: (data) => data.map(p => ({
...p,
excerpt: p.content?.substring(0, 150) + '...',
})),
default: () => [], // 初始默认值
});
// useAsyncData:包装任意异步逻辑
const { data: stats } = await useAsyncData('stats', () =>
$fetch('/api/stats')
);
useHead({ title: '首页 - 我的博客' });
</script>
<template>
<div v-if="pending">加载中...</div>
<div v-else-if="error">{{ error.message }}</div>
<template v-else>
<article v-for="post in posts" :key="post.id">
<h2>{{ post.title }}</h2>
<p>{{ post.excerpt }}</p>
</article>
<button @click="refresh">刷新</button>
</template>
</template>
Nuxt Server API
文件名即路由,开发体验一流
定义:在 server/api/ 目录下创建文件,文件名就是 API 路径。.get.ts 处理 GET,.post.ts 处理 POST,基于 h3 事件引擎。
// server/api/users.get.ts — 对应 GET /api/users
import { defineEventHandler, getQuery } from 'h3';
export default defineEventHandler(async (event) => {
const query = getQuery(event);
const page = Number(query.page) || 1;
const limit = Number(query.limit) || 10;
const [users, total] = await Promise.all([
event.context.prisma.user.findMany({
skip: (page - 1) * limit, take: limit,
select: { id: true, name: true, email: true },
}),
event.context.prisma.user.count(),
]);
return { users, total, page, limit };
});
Server Middleware
请求到达 API 之前的拦截器
定义:server/middleware/ 下的文件会在每个 API 请求之前执行,用于鉴权、日志、CORS 等跨切面逻辑。
白话:和 Next.js 的 middleware 类似,但 Nuxt 的运行在 Node.js(或 Nitro 运行时)中,可以使用完整的 Node.js API。
// server/middleware/auth.ts
import { defineEventHandler, createError, getHeader } from 'h3';
export default defineEventHandler((event) => {
const url = event.path;
// 跳过公开路由
if (url.startsWith('/api/auth') || url.startsWith('/api/public')) {
return;
}
const token = getHeader(event, 'authorization')?.replace('Bearer ', '');
if (!token) {
throw createError({ statusCode: 401, message: '未提供认证令牌' });
}
// 将解析后的用户信息挂到 event.context
event.context.user = verifyToken(token);
});
Nuxt 的 Server Middleware 基于 Nitro 引擎,支持部署到 Node.js、Cloudflare Workers、Vercel Edge 等多种目标平台。通过 nuxt.config.ts 中的 nitro.preset 配置切换。
Part 3: Remix
由 Shopify 支持的 React 全栈框架。核心理念是回归 Web 标准——充分利用 FormData、Fetch API、HTTP 缓存等浏览器原生能力。
- 渐进增强——即使 JavaScript 被禁用,表单提交仍然可以工作
- Loader / Action 模式——每个路由模块导出 loader(读数据)和 action(写数据)
- 嵌套路由——精确控制数据重获取范围,不浪费网络请求
- 适合场景——内容管理、电商、需要高可靠性的表单密集型应用
Remix 路由与数据
Loader 读数据,Action 写数据
// app/routes/users.tsx
import { json } from '@remix-run/node'; // 服务端工具
import { Form, useLoaderData } from '@remix-run/react'; // 客户端组件
import type { ActionFunction, LoaderFunction } from '@remix-run/node';
// Loader:服务端获取数据(类似 GET handler)
export const loader: LoaderFunction = async () => {
const users = await db.user.findMany();
return json({ users });
};
// Action:处理表单提交(类似 POST handler)
export const action: ActionFunction = async ({ request }) => {
const formData = await request.formData();
const name = formData.get('name') as string;
const email = formData.get('email') as string;
await db.user.create({ data: { name, email } });
return json({ ok: true });
};
// UI:默认是服务端渲染
export default function Users() {
const { users } = useLoaderData<typeof loader>();
return (
<div>
<h1>用户列表</h1>
<ul>{users.map(u => <li key={u.id}>{u.name} - {u.email}</li>)}</ul>
{/* 原生 Form,JS 禁用时也能提交 */}
<Form method="post">
<input name="name" placeholder="姓名" required />
<input name="email" type="email" placeholder="邮箱" required />
<button type="submit">添加用户</button>
</Form>
</div>
);
}
Remix 的 <Form> 组件在 JS 加载后会用 fetch 拦截提交(体验更好),JS 没加载时则退化为原生 HTML form 提交(仍然可用)。这就是渐进增强。
Part 4: SvelteKit
Svelte 官方全栈框架。核心卖点是编译时优化——Svelte 在构建阶段就把响应式逻辑编译成高效的命令式 DOM 操作,客户端 JS 体积极小。
- 零虚拟 DOM——编译后直接操作 DOM,运行时开销几乎为零
- Load 函数——每个路由的
+page.ts导出 load 函数获取数据 - Actions——
+page.server.ts中的 form actions 处理表单提交 - 适配器模式——通过 adapter 部署到 Vercel、Netlify、Cloudflare 等
- 适合场景——对性能极致要求的 SPA、交互密集型应用、PWA
SvelteKit 路由与数据
load 读数据,actions 写数据
// src/routes/users/+page.server.ts
import type { PageServerLoad, Actions } from './$types';
// Load:服务端获取数据
export const load: PageServerLoad = async () => {
const users = await db.user.findMany({
select: { id: true, name: true, email: true },
});
return { users };
};
// Actions:处理表单提交
export const actions: Actions = {
create: async ({ request }) => {
const data = await request.formData();
const name = data.get('name') as string;
const email = data.get('email') as string;
await db.user.create({ data: { name, email } });
return { success: true };
},
};
<!-- src/routes/users/+page.svelte -->
<script lang="ts">
// $page.data 自动接收 load 返回的数据
let { data } = $props();
</script>
<h1>用户列表</h1>
<ul>
{#each data.users as user}
<li>{user.name} - {user.email}</li>
{/each}
</ul>
<form method="POST" action="?/create">
<input name="name" placeholder="姓名" required />
<input name="email" type="email" required />
<button type="submit">添加用户</button>
</form>
四大框架对比
选哪个?看你的 UI 偏好
| 特性 | Next.js | Nuxt 3 | Remix | SvelteKit |
|---|---|---|---|---|
| UI 库 | React | Vue 3 | React | Svelte |
| 服务端渲染 | SSR/SSG/ISR | SSR/SSG/SWR | SSR | SSR/SSG |
| 自动导入 | 不支持 | 组件自动导入 | 不支持 | 部分支持 |
| 部署 | Vercel / Node | Nitro 多目标 | 任意 Node | 适配器模式 |
| 社区规模 | 最大 | 大 | 中等 | 增长中 |
| 学习曲线 | 中等 | 低 | 中等 | 低 |
| 客户端体积 | 较大 | 中等 | 中等 | 极小 |
框架选型指南
根据项目需求做决策
| 你的情况 | 推荐框架 | 理由 |
|---|---|---|
| 团队熟悉 React,需要灵活的渲染策略 | Next.js | SSR/SSG/ISR 全支持,生态最大 |
| 团队熟悉 Vue,追求开发效率 | Nuxt 3 | 自动导入 + Nitro 多平台部署 |
| 重视 Web 标准、表单密集型应用 | Remix | 渐进增强、离线可用、FormData 原生 |
| 对性能和包体积极致追求 | SvelteKit | 编译优化、客户端代码极小 |
| 企业项目,合规要求自托管 | Next.js / Nuxt 3 | Docker 部署成熟、社区资源多 |
| 个人博客或文档站 | Nuxt 3 / SvelteKit | 配置少、上手快、静态导出好 |
核心决策因素:1) 团队最熟悉哪个 UI 库(React / Vue / Svelte);2) 项目对 SEO、性能、离线能力的要求;3) 部署环境限制。框架本身没有绝对优劣,选你团队最顺手的那个。
排错:常见问题
useState,页面报错 "useState is not defined"。
原因:Server Component 默认运行在服务端,不支持 React 客户端 Hook。
修复:把需要交互的子组件拆出来,加上
'use client' 声明。
useFetch 的数据在客户端重复请求。
原因:没有设置
key,或者 key 在服务端和客户端不一致。
修复:给
useFetch 设置稳定的 key,如 useFetch('/api/posts', { key: 'posts' })。
原因:过度依赖框架的专有 API,没有做好抽象。
修复:核心业务逻辑放在独立模块中,框架只负责路由和渲染。如果后端需要消息队列、定时任务等复杂逻辑,用独立 Node.js 服务。
练习一:Next.js
任务
创建一个 Next.js App Router 项目,实现一个博客首页:使用 Server Component 从数据库获取文章列表,支持 ISR 每 10 分钟重新验证。
$ npx create-next-app@latest my-blog --typescript --app
$ cd my-blog
$ npm install @prisma/client
$ npx prisma init
参考答案
// app/page.tsx
export const revalidate = 600; // ISR: 每 10 分钟重新验证
export default async function Home() {
const posts = await fetch('https://api.example.com/posts', {
next: { revalidate: 600 },
}).then(r => r.json());
return (
<main>
<h1>我的博客</h1>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</main>
);
}
练习二:Nuxt 3
任务
创建一个 Nuxt 3 项目,实现用户列表页面:使用 useFetch 从 Server API 获取数据,并添加一个搜索过滤功能(Client 侧交互)。
$ npx nuxi@latest init my-nuxt-app
$ cd my-nuxt-app
参考答案
<!-- pages/users.vue -->
<script setup lang="ts">
const { data: users } = await useFetch('/api/users');
const search = ref('');
const filtered = computed(() =>
users.value?.filter(u =>
u.name.toLowerCase().includes(search.value.toLowerCase())
)
);
</script>
<template>
<div>
<input v-model="search" placeholder="搜索..." />
<ul>
<li v-for="u in filtered" :key="u.id">
{{ u.name }} - {{ u.email }}
</li>
</ul>
</div>
</template>
练习三:框架对比
任务
选择 Remix 或 SvelteKit(二选一),创建一个简单的待办事项应用。要求:1) 使用路由 load/loader 获取数据;2) 使用 action/form actions 添加待办事项。
参考答案(SvelteKit 版)
// src/routes/todos/+page.server.ts
let todos = [
{ id: 1, text: '学习 SvelteKit', done: false },
];
export const load = async () => ({ todos });
export const actions = {
add: async ({ request }) => {
const data = await request.formData();
const text = data.get('text') as string;
todos.push({ id: Date.now(), text, done: false });
return { success: true };
},
};
📝 要点总结
本章核心知识点
渲染策略
SSR 首屏快、SSG 性能优、ISR 兼顾两者,按页面特性选择
Next.js
Server Components 减少客户端 JS,App Router 统一路由范式
Nuxt 3
自动导入 + 组合式 API + 全栈能力,Vue 生态的首选
选型建议
React 生态选 Next.js,Vue 选 Nuxt,追求极简选 SvelteKit
下一章
全栈框架让前后端融为一体。下一章我们学习 tRPC——一种让前后端类型自动同步的神奇框架。