来源:notes/documents/个人助理网页版框架改造总览:三档候选、迁移代价与结论.md

个人助理网页版框架改造总览:三档候选、迁移代价与结论

结论

换框架解决不了这个项目当前遇到的任何一个问题。 现在的痛点只有两个,都不在框架层:

  1. 表结构的 DDL 与 Drizzle 定义是两份、必须人手一致(scripts/migrate-calendar-entries.mjsbackend/src/lib/schema.ts)——这个用 Drizzle Kit 就能消灭,不必换框架。
  2. 没了。低频、单用户、只读,没有并发、没有写路径、没有陌生人。

如果一定要动,按代价分三档:

做什么 代价 什么时候值得
一档:只换单层 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.jsonoperations.md
3 OAuth 回调地址能从请求的 Host 推导(预览域名与生产域名各自可跑) backend/src/http.tsrequestOrigin
4 构建期能读仓库根 notes/**/*.md(不是框架约定的 content 目录),产物进函数包 scripts/generate-notes.mjsvercel.jsonincludeFiles
5 笔记正文不进前端 bundle,首屏只拉元数据 operations.md 坑 5
6 服务端能跑 Node 运行时(用 node:cryptonode:fs,Neon 走 neon-http backend/src/lib/*.ts
7 鉴权继续是自签发、服务端可撤销的 Session,不被迫换成 JWT 或托管身份 web/docs/adr/0003
8 单用户、只读、没有写接口,不需要 CRUD 生成器 / 表单动作 web/docs/adr/00040005
9 前端状态就是查询参数(?m=&n=&o=&d=&q=),不能被文件系统路由取代 web/docs/adr/0010
10 设计 token 与外壳契约(脊 64 / 索引 300 / 页边 72)要能原样表达 web/docs/adr/0009frontend/src/index.css
11 测试可以继续零框架依赖(node:test + 类型剥离) web/package.jsontests/
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-kit 0.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/vercelhandle(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」 「不 VaryCookie 这类高基数头」两条,11 个基于 session cookie 的接口全都不满足。硬加就有 把一个人的响应发给另一个人的风险。静态资源不受影响,本来就走默认缓存。
  • Runtime Cache 是唯一为函数内数据设计的、与框架无关的缓存(@vercel/functionsgetCache), 但官方写明计费、单条 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/vite 4.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 默认 nodejsedge 已 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 改名 proxypreferredRegion 废弃(现有 regions: ["sin1"] 写法要重想) 11 个 api/*.ts 要搬进路由表;Vercel 侧建议装 @vercel/react-routervercelPreset() 默认给 server function 装同源 CSRF 校验,GitHub 的跨站 OAuth 回调正好被它拒掉;Nitro 顶层 /api 与 Vercel 不兼容

选 React Router(framework mode),理由是它与现有项目的契约最省改动:保留 Vite、服务端入口就是 (request: Request) => Responserequest.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):

  • 官方支持构建期读任意目录的 Markdownglob() loader 的 base 相对 config.root 解析,所以直接写 base: '../notes';pattern 不允许 ../ 开头,官方会明确报错并让你改用 base。这条能力是从官方发布包 构建产物里读到的(new URL(globOptions.base, config.root)),不是文档措辞。front matter 的 note_id / created_at 用 Zod 在 defineCollection 里声明即可。
  • output 现在只有 staticserver(v4 的 hybrid 已并入 static,即 static 本身就允许 逐路由 prerender = false),因此「笔记静态出网 + 11 个接口按需」是开箱形态。
  • 默认落 Vercel Functions(Node.js serverless),Edge 只用于 middlewareMode: 'edge' 的中间件; adapter 没有 region 配置项,所以现有 vercel.jsonregions: ["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

迁移前的两个「必须先复核」的环境事实

  1. 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 然后给出空集合。
  2. 构建期产物最终以什么形态、多大体积进入部署,三个框架的官方文档都没说清:Astro 只说 data store 是 .astro/data-store.json,SvelteKit 只说 prerender 路由会移出 SSR manifest,Nuxt 干脆改成运行时数据库。 这是唯一需要在实施前拿真实数据试探的地方。

如果真要动:建议的顺序

  1. 先做 Drizzle Kit(半天级、纯收益、可单独回滚),顺手消掉「两处定义必须一致」。
  2. 想做交互升级但不换框架:按需上 React Router 的 library 模式;样式层若要 Tailwind v4 + shadcn/ui, 先想清楚要不要动 adr/0009 的外壳契约,否则会变成「新组件语言 + 旧 token 体系」两层皮。
  3. 想换框架:愿意接受 SPA → MPA 的重写就选 Astro(它最懂「内容」);否则选 React Router framework mode (它最懂「请求」,且与现有 handler 同签名)。
  4. 无论哪档:动了就要同步改 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