来源:
notes/documents/个人助理网页版框架改造总览:三档候选、迁移代价与结论.md个人助理网页版框架改造总览:三档候选、迁移代价与结论
结论
换框架解决不了这个项目当前遇到的任何一个问题。 现在的痛点只有两个,都不在框架层:
- 表结构的 DDL 与 Drizzle 定义是两份、必须人手一致(
scripts/migrate-calendar-entries.mjs与backend/src/lib/schema.ts)——这个用 Drizzle Kit 就能消灭,不必换框架。 - 没了。低频、单用户、只读,没有并发、没有写路径、没有陌生人。
如果一定要动,按代价分三档:
| 档 | 做什么 | 代价 | 什么时候值得 |
|---|---|---|---|
| 一档:只换单层 | Drizzle Kit(★唯一明确净收益);可选再上 Hono | 小 | 任何时候都值得做 Drizzle Kit 这一步 |
| 二档:换 React 全栈框架 | React Router framework mode(Next.js / TanStack Start 见下文理由排除) | 中:11 个接口搬进路由表、Vercel preset 变更 | 想把「接口 + 页面」收进一套路由与数据加载模型时 |
| 三档:换内容优先栈 | Astro(SvelteKit / Nuxt 排除) | 大:SPA → 多页 + islands 的交互重写,外加 1163 行手写 CSS 重表达 | 想彻底告别 SPA、让笔记以 HTML 形态直接出网时 |
明确不建议的四条,每条都有下文的一手依据:换鉴权框架、加缓存层、把正文搬进数据库做全文检索、 为了「框架化」去上 Next.js。
说明:这一档里 React 全栈框架那部分另有一篇深挖——
个人助理网页版框架改造选型调研:React 全栈框架 (Next.js / React Router / TanStack Start),逐框架回答了 9 个问题;本文只摘结论与横向对比。
评标尺:这个项目对框架提出的 12 条硬要求
先把「能不能换」变成可判定的问题。每条都能在仓库里找到出处,换任何框架都要逐条过:
| # | 要求 | 出处 |
|---|---|---|
| 1 | 前后端在同一个 Vercel 项目里、且同域(Session 是 httpOnly + SameSite=Lax cookie) | web/docs/adr/0001 |
| 2 | 能回到现有 URL 形态:前端静态出口 frontend/dist,接口在 /api/* 下 |
vercel.json、operations.md |
| 3 | OAuth 回调地址能从请求的 Host 推导(预览域名与生产域名各自可跑) | backend/src/http.ts 的 requestOrigin |
| 4 | 构建期能读仓库根 notes/**/*.md(不是框架约定的 content 目录),产物进函数包 |
scripts/generate-notes.mjs、vercel.json 的 includeFiles |
| 5 | 笔记正文不进前端 bundle,首屏只拉元数据 | operations.md 坑 5 |
| 6 | 服务端能跑 Node 运行时(用 node:crypto、node:fs,Neon 走 neon-http) |
backend/src/lib/*.ts |
| 7 | 鉴权继续是自签发、服务端可撤销的 Session,不被迫换成 JWT 或托管身份 | web/docs/adr/0003 |
| 8 | 单用户、只读、没有写接口,不需要 CRUD 生成器 / 表单动作 | web/docs/adr/0004、0005 |
| 9 | 前端状态就是查询参数(?m=&n=&o=&d=&q=),不能被文件系统路由取代 |
web/docs/adr/0010 |
| 10 | 设计 token 与外壳契约(脊 64 / 索引 300 / 页边 72)要能原样表达 | web/docs/adr/0009、frontend/src/index.css |
| 11 | 测试可以继续零框架依赖(node:test + 类型剥离) |
web/package.json、tests/ |
| 12 | 本机 *.vercel.app 被 DNS 污染,本地开发/预览不能强依赖那个域名 |
operations.md 坑 1 |
代价基数:换框架要动多少代码(2026-09-23 实测)
| 部分 | 规模 | 换框架时要不要重写 |
|---|---|---|
接口入口 api/ |
11 个文件 / 251 行 | 逻辑可搬,handler 形态要改 |
业务代码 backend/ |
805 行 | 基本可搬,鉴权与 Cookie 部分看框架 |
前端 frontend/src/ |
20 个文件 / 2943 行 | 重写,其中 1163 行是手写 CSS |
构建脚本 scripts/ + 测试 tests/ |
2083 行 | 产物生成与本地预览要重做,测试跟着框架换 |
| 数据 | 笔记 118 篇 + 其他文档 47 篇,正文合计 7.39 MiB | 不动 |
一档:只换单层(不换框架)
Drizzle Kit —— 唯一明确的净收益
现在的痛点是「同一张表写了两遍」:手写幂等 DDL 为了建表,schema.ts 为了运行时类型,靠人守着。
Drizzle Kit 让 schema.ts 同时成为运行时类型与迁移 SQL 的来源,diff 由工具做:
| 命令 | 干什么 | 要不要连库 |
|---|---|---|
generate |
读 schema → 与上次 snapshot 比对 → 产出 migration.sql + snapshot.json |
不要 |
migrate |
读迁移文件 + 库里的 migration log → 跑没跑过的 | 要 |
push |
不产 SQL 文件,直接把 diff 推给库 | 要 |
pull |
从库反推 schema.ts(旧名字 introspect 已从官方命令列表消失) |
要 |
落地方案:drizzle/ 目录进仓库(官方设计如此),migrate 由本机脚本手动跑、别放进函数启动路径;
Neon 官方建议迁移用 direct(非 pooled)连接串。代价是仓库多一个迁移目录、库里多一张 drizzle 的
migrations log 表。另有 check(只查迁移文件之间的冲突,不是 schema↔库一致性校验)与 studio。
- https://orm.drizzle.team/docs/kit-overview ・ https://orm.drizzle.team/docs/drizzle-kit-generate
- https://orm.drizzle.team/docs/drizzle-kit-migrate
- 版本:
drizzle-kit0.31.11(npm registry,2026-09-23)
Hono —— 小赚,不必要
能立刻省掉的是 backend/src/http.ts 那 55 行手写件:cookie(hono/cookie)、302(c.redirect)、
CORS、basic auth,测试也从「自己构造 Request」变成 app.request()。挂载无阻碍:
hono/vercel 的 handle(app) 官方实现就是 (req) => app.fetch(req),可以逐个替换现有 api/**/*.ts,
不必改成「整站一个函数」。代价是多一个依赖与一套路由/中间件心智——而现在是 11 个扁平、不共享中间件的接口。
一个反例:Fastify / Express 在 Vercel 是「整站一个 Function」,入口文件名必须叫 app|index|server,
与现在「每个接口独立函数」的形态冲突,属于倒退。
- https://hono.dev/docs/getting-started/vercel ・ https://vercel.com/docs/frameworks/backend/hono
- https://github.com/honojs/hono/blob/main/src/adapter/vercel/handler.ts ・ 版本 4.13.8
鉴权 —— 换就是净负债
现有实现(3 张表 + SHA-256 token + httpOnly cookie + 30 天滚动 + 服务端可撤销)在功能上已经等价于 「数据库 session 模式」的框架默认行为,换框架只是把同一套逻辑交给别人的代码,而每条候选都不合身:
| 候选 | 一手事实 | 判决 |
|---|---|---|
| Auth.js / NextAuth v5 | 安装页的框架分支只有 Next / Qwik / SvelteKit / Express;官方明写 @auth/core「as a user you should never have to interact with」 |
在 Vercel 手写 handler 里用它属于文档外用法 |
| Lucia | README 第一句:deprecated on March 2025;npm lucia@3.2.2 带 deprecation,最后发布 2024-10-20 |
不适用 |
| Clerk / WorkOS | Hobby / AuthKit 免费额度对单用户绰绰有余(Clerk 50k MRU、WorkOS 100 万用户内 $0),但身份与会话数据落到第三方云 | 免费但不减代码,且违反「数据不出境」 |
| Better Auth(自托管) | 数据库 session 为默认,有 Drizzle adapter、GitHub provider、revokeSession、session 表内置 ip + userAgent;版本 1.7.5 |
形态吻合,但替换掉的正是现有那 3 张表;且开 cookieCache 会让撤销延迟生效 |
| Neon Managed Better Auth | Free 档含 60,000 MAU(不是付费专属),装在自己的 neon_auth schema 里;但仅 AWS region、不支持 IP Allow / Private Networking、GitHub 需自备 OAuth App |
想减掉 OAuth + session 代码时可用;代价是把鉴权变成托管依赖 + 控制台配置 |
- https://authjs.dev/getting-started/installation ・ https://github.com/lucia-auth/lucia
- https://www.better-auth.com/docs/concepts/session-management ・ https://neon.com/docs/auth/overview
- https://clerk.com/pricing ・ https://workos.com/pricing
缓存与搜索 —— 现在不该动
- CDN 缓存(
s-maxage)对本项目的接口用不了:Vercel 官方列的准入条件里,「响应不含set-cookie」 「不Vary到Cookie这类高基数头」两条,11 个基于 session cookie 的接口全都不满足。硬加就有 把一个人的响应发给另一个人的风险。静态资源不受影响,本来就走默认缓存。 - Runtime Cache 是唯一为函数内数据设计的、与框架无关的缓存(
@vercel/functions的getCache), 但官方写明计费、单条 2 MB、Hobby 下全项目共享一个 cache。对「冷启动解析 1.76 MiB ≈ 80 ms」的 量级,收益小于多一个计费面。 -
全文检索的前提是正文进库,而正文现在是构建产物。
tsvector+ GIN、pg_trgm技术上都成立, 但要先增加一条「笔记正文 → Postgres」的投影链路。官方只给了「随着数据增长」这类定性说法, 没有给出「多少篇 / 多少 MB 才值得换」的阈值。另外 Neon 的pg_search(BM25)已弃用: 2026-03-19 起新项目不可用、2026-09-21 起既有项目移除。 - https://vercel.com/docs/caching/cdn-cache ・ https://vercel.com/docs/caching/runtime-cache
- https://neon.com/guides/full-text-search ・ https://www.postgresql.org/docs/current/pgtrgm.html
- https://neon.com/docs/extensions/pg_search
前端内部的小换法(不换框架也能拿到)
- 路由:React Router 有三种模式——Framework / Data / Declarative;只想要一个路由库,装
react-router用 Declarative 或 Data 模式即可,不必上 framework mode。但对本项目意义有限:现在的「查询参数即状态」 是刻意的取舍(adr/0010),换成路由库后 URL 形态与?n=这类深链要重新设计。 - 样式与组件:Tailwind 现在是 v4,官方有 Vite 插件(
@tailwindcss/vite)、CSS-first 配置 (@theme写在 CSS 里,不再需要tailwind.config.js)、自动探测 class 来源;shadcn/ui 的组件是拷进仓库 的源码,官方有 Vite 指南,前提是已装 Tailwind 并配好@/*别名。代价是要把 1163 行手写 CSS 与 「脊 64 / 索引 300 / 页边 72」的外壳契约重新表达一遍——收益是组件层的复用,而这个站点的界面语汇本来就 不属于任何通用组件库,所以这一步的性价比取决于以后要不要加大量新模块。 - 版本:
tailwindcss/@tailwindcss/vite4.3.3(npm registry,2026-09-23)
二档:React 全栈框架(要动前端时,代价最小的那一档)
| Next.js(App Router) | React Router(framework mode) | TanStack Start | |
|---|---|---|---|
| 版本与成熟度 | 16.3.6(2026-09-22),稳定 | 8.4.0(2026-09-15),稳定(v7 线 7.18.4) | npm 1.168.57,但官方仍标 Release Candidate |
| 运行时 | Route Handler 默认 nodejs,edge 已 deprecated |
Vercel Functions(Node),支持 Fluid compute | 经 Nitro(官方示例钉 nitro@3.0.1-alpha.0) |
构建期读仓库根 notes/ |
能,但要配 outputFileTracingRoot / outputFileTracingIncludes |
能且最自然(react-router.config.ts 就是构建期 Node 脚本) |
能,但官方建议改用 content-collections |
| 拿到浏览器访问的 host | headers() / x-forwarded-host |
request.url,与现 handler 同签名 |
server route 可以;server function 不行 |
| 首屏数据模型 | RSC + Cache Components | loader + resource route |
loader(默认同构)+ server function |
| 测试 | 官方 Vitest 指南 | 官方 createRoutesStub + RTL |
官方无 testing 指南 |
| 对项目的坑 | 需顺带把 React 18 升到 canary(19);middleware 改名 proxy、preferredRegion 废弃(现有 regions: ["sin1"] 写法要重想) |
11 个 api/*.ts 要搬进路由表;Vercel 侧建议装 @vercel/react-router 的 vercelPreset() |
默认给 server function 装同源 CSRF 校验,GitHub 的跨站 OAuth 回调正好被它拒掉;Nitro 顶层 /api 与 Vercel 不兼容 |
选 React Router(framework mode),理由是它与现有项目的契约最省改动:保留 Vite、服务端入口就是
(request: Request) => Response、request.url 天然给出访问 host、resource route 可以直接返回
JSON / 302 / Set-Cookie。三条约束(1、2、3、6)不用重想。
三档:内容优先栈(要动前端且接受换范式时)
Astro 是这一档里唯一贴合「内容站」本质的(astro 7.3.4 / @astrojs/vercel 11.0.11 /
@astrojs/react 7.0.0,均 2026-09-22):
- 官方支持构建期读任意目录的 Markdown:
glob()loader 的base相对config.root解析,所以直接写base: '../notes';pattern 不允许../开头,官方会明确报错并让你改用base。这条能力是从官方发布包 构建产物里读到的(new URL(globOptions.base, config.root)),不是文档措辞。front matter 的note_id/created_at用 Zod 在defineCollection里声明即可。 output现在只有static与server(v4 的hybrid已并入static,即static本身就允许 逐路由prerender = false),因此「笔记静态出网 + 11 个接口按需」是开箱形态。- 默认落 Vercel Functions(Node.js serverless),Edge 只用于
middlewareMode: 'edge'的中间件; adapter 没有 region 配置项,所以现有vercel.json的regions: ["sin1"]可原样保留。 - 能原地复用现有 React 组件(
@astrojs/react,同页可混用多框架),按 island 精确 hydrate。 - 三个具体的坑:① 内容集合默认把每篇 Markdown 渲染成 HTML 存进 data store,官方点名大集合可能构建期
内存爆掉,要用
deferRender/retainBody: false(必要时collectionStorage: 'chunked'); ②astro:assets管不到项目目录之外的图片,现有「拷贝 + 重写 URL」脚本要原样保留; ③ Astro 没有官方鉴权方案,回调 origin 要改用 Vercel 的x-forwarded-host,且Astro.cookies只在 按需渲染路由上可用。 - 最本质的代价:从「查询参数即路由的单 React 树」变成「多页 + islands」,props 必须可序列化、 跨 island 不能靠 React context——这是交互层的重写,不是技术替换。
SvelteKit(@sveltejs/kit 2.70.3 / adapter-vercel 6.3.4):后端能力能承接(含 hooks.server.ts 做会话),
但它没有 content layer,构建期读 ../notes 要用 import.meta.glob 或 prerender 自己搭;adapter 官方
Troubleshooting 明写文件系统「files are not copied from your project into your deployment」,所以「正文按篇取」
必须走 prerender 静态文件或打进产物,不能像现在这样让函数读盘;React 组件要全量改写成 Svelte。
Nuxt(4.5.2 / @nuxt/content 3.16.1):source.cwd 确实能指向项目外目录,但它的内容模型是数据库——
Vercel 上默认把 SQLite 放在 /tmp(函数只读,/tmp 只是临时空间),官方因此要求改用 D1 / LibSQL / Postgres
这类外部库。等于给一个纯只读站点凭空加一层数据依赖;另外 Nitro 的顶层 /api 与 Vercel 不兼容。
- https://docs.astro.build/en/reference/content-loader-reference/ ・ https://docs.astro.build/en/guides/integrations-guide/vercel/
- https://docs.astro.build/en/guides/content-collections/ ・ https://svelte.dev/docs/kit/adapter-vercel
- https://content.nuxt.com/docs/deploy/vercel ・ https://nitro.build/deploy/providers/vercel
迁移前的两个「必须先复核」的环境事实
- Vercel 官方两处文档口径不一致:
configure-a-build写「Root Directory 之外的文件不可访问、不能用..」,collaborative 的 monorepo FAQ 写「打开 Include source files outside of the Root Directory in the Build Step 即可,2020-08-27 之后创建的项目默认开启」。现项目能读notes/就靠这个开关 (内部operations.md有记录,REST API 字段名是sourceFilesOutsideRootDirectory,没有面向用户的 正式文档)。换框架或重建 Vercel 项目前必须以项目实际设置为准复核——Astro 那边写错路径不会报错, 只会记一条 warn 然后给出空集合。 - 构建期产物最终以什么形态、多大体积进入部署,三个框架的官方文档都没说清:Astro 只说 data store 是
.astro/data-store.json,SvelteKit 只说 prerender 路由会移出 SSR manifest,Nuxt 干脆改成运行时数据库。 这是唯一需要在实施前拿真实数据试探的地方。
如果真要动:建议的顺序
- 先做 Drizzle Kit(半天级、纯收益、可单独回滚),顺手消掉「两处定义必须一致」。
- 想做交互升级但不换框架:按需上 React Router 的 library 模式;样式层若要 Tailwind v4 + shadcn/ui,
先想清楚要不要动
adr/0009的外壳契约,否则会变成「新组件语言 + 旧 token 体系」两层皮。 - 想换框架:愿意接受 SPA → MPA 的重写就选 Astro(它最懂「内容」);否则选 React Router framework mode (它最懂「请求」,且与现有 handler 同签名)。
- 无论哪档:动了就要同步改
adr/0001(同域单项目)、0003(自签发 Session)、0009(外壳契约)、0010(查询参数路由)里被触碰的那几条,别让 ADR 与代码脱钩。
来源与口径
- 调研日期 2026-09-23;所有版本号取自 npm 官方 registry(
/latest),所有行为取自官方文档 / 官方仓库 / 官方发布包产物。 - 深挖文档:React 全栈框架那一档见
个人助理网页版框架改造选型调研:React 全栈框架(Next.js / React Router / TanStack Start);Astro / SvelteKit / Nuxt 与四层替换项的完整证据链(含 130+ 条官方 URL 与 「未能从一手资料确认」清单)留在调研中间产物里,未入库。 - 关键一手来源:
- Next.js:https://nextjs.org/docs ・ React Router:https://reactrouter.com/home ・ TanStack Start:https://tanstack.com/start
- Astro:https://docs.astro.build/en/guides/content-collections/ ・ Vercel 框架支持:https://vercel.com/docs/frameworks
- Vercel Functions 限制:https://vercel.com/docs/functions/limitations ・ 区域:https://vercel.com/docs/functions/configuring-functions/region
- 缓存准入:https://vercel.com/docs/caching/cdn-cache ・ Root Directory:https://vercel.com/docs/deployments/configure-a-build
- Drizzle:https://orm.drizzle.team/docs/kit-overview ・ Hono:https://hono.dev/docs/getting-started/vercel
- 鉴权:https://authjs.dev/getting-started/installation ・ https://www.better-auth.com/docs/concepts/session-management ・ https://neon.com/docs/auth/overview