news 2026/10/14 16:39:00

AnimeGarden Web 前端工程解析:TanStack Start 页面服务与 Hono Node 外壳的运行、构建与部署

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AnimeGarden Web 前端工程解析:TanStack Start 页面服务与 Hono Node 外壳的运行、构建与部署
  • 后端
  • 前端
  • 网页爬虫
  • MCP 服务
  • AI 技能

【免费下载链接】AnimeGarden

動漫花園 镜像站 | 动画 BT 资源聚合站 | 动画 BT 资源开放接口

项目地址:https://gitcode.com/gh_mirrors/an/AnimeGarden
点击查看免费下载

apps/web是 AnimeGarden 的 TanStack Start 前端应用,它负责资源浏览、搜索、动画周历、收藏夹、详情页、sitemap 输出与 OpenAPI 文档页等全部浏览器侧能力。本篇以 apps/web/README.md 的核心信息为骨架,结合 server.mjs、vite.config.ts、Dockerfile 与 fly.toml 等实际配置,讲清这个应用的三条命令如何跑起来、生产 Node 入口如何分层处理请求、构建期环境变量如何注入,以及 Fly.io 部署链路的完整形态,读完即可独立理解、运行并部署 AnimeGarden 的前端服务。

一、包定位:TanStack Start 前端,API 由独立服务承担

README 对@animegarden/web的定位只有一句话,但它蕴含了一条明确的架构边界:

TanStack Start frontend for AnimeGarden. Web intentionally returns 404 for/api/*; use the standalone API/feed service instead.

也就是说,Web 应用本身不再承担任何 API 读写与 feed 输出。动漫花园、蜜柑计划、萌番组、ANi 四个站点的资源数据读写与 RSS 由独立的 server/feed 服务提供(对应仓库中的 apps/server 与 packages/client),前端仅通过@animegarden/client指向公共 API 域名发起数据请求。这条边界在代码里有多处可验证:

  • server.mjs 对/api/*一律返回 404 且带Cache-Control: no-store,从 HTTP 层面杜绝 Web 被误当作 API 代理:

    app.all('/api/*', cors(), (c) => { return c.text('Not Found', 404, { 'Cache-Control': 'no-store' }); });
  • 数据读取实际发生在 TanStack Start 的 server functions 中。src/query/proxy.ts 用createServerFn({ method: 'GET' })封装了fetchResources、fetchResourceDetail、fetchCollection、generateCollection等调用,其baseURL优先取WEB_SERVER_URL(SSR 环境下直连内网后端),否则回落到https://${FEED_HOST}/公共 API 域名,并对 502/503/504 的 GET 请求做一次透明重试。

  • 更完整的请求与数据流约定记录在 docs/web/architecture-overview.md,其中明确了 Web 不再提供/api/*、/feed.xml或/collection/:hash/feed.xml代理。

二、开发、构建与运行:三条命令及其背后机制

README 给出的全部命令如下,均从 monorepo 根目录通过pnpm -C定位到apps/web:

pnpm -C apps/web dev pnpm -C apps/web build pnpm -C apps/web start:node

对照 apps/web/package.json 的scripts字段,可以看到每条命令的真实形态与适用场景:

命令实际执行说明
devVIRTUAL_PWA_MODULE=true vite devVite 开发服务器 + TanStack Start 开发模式
buildVIRTUAL_PWA_MODULE=true vite build产出dist/client(静态资源)与dist/server/server.js(SSR 入口)
start:node/previewnode server.mjs以 Hono Node 外壳托管生产产物并监听端口
test/test:civitest/vitest --run前端测试(如 test/start-etag.test.ts)
typechecktscTypeScript 检查

几个值得展开的细节:

  1. VIRTUAL_PWA_MODULE=true。dev与build都显式设置了这个环境变量,用于在本地开发时以虚拟模块方式屏蔽 PWA 构建依赖,保证不依赖完整 PWA 产物也能完成构建流程。
  2. monorepo 层的等价入口。根目录 package.json 提供pnpm build:web(turbo run build --filter @animegarden/web...)与pnpm dev:web(即pnpm -C apps/web dev),pnpm start:web则等价于node apps/web/server.mjs。生产镜像内部使用的是 turbo 的build:web。
  3. 构建目标与模块解析。vite.config.ts 中build.target为es2022、minify: true,resolve.mainFields为['browser', 'module', 'main'],别名@指向./src;optimizeDeps.exclude排除了@animegarden/server与 jieba 相关包,避免 Vite 预构建原生依赖。

三、生产 Node 入口 server.mjs:一个 Hono 外壳如何分层处理请求

README 的核心表述是:“The production Node entry isserver.mjs. It serves Start assets/pages and/health.” 把 server.mjs 完整展开,它的请求处理顺序是:

  1. 请求日志:对所有路由挂hono/logger,经consola带Web标签输出。
  2. /health健康检查:GET/任意方法均返回{ "status": "OK" },并带 CORS。这是 Fly 平台存活探测的端点(见第五节 fly.toml 的checks配置)。
  3. /api/*显式 404:如第一节所述,与独立 API/feed 服务的边界在入口层就被强制。
  4. 静态资源:优先探测.output/public/,否则使用dist/client/作为ClientRoot;根目录下的每个文件(favicon、site.webmanifest、twitter.jpg等 public/ 中的文件)逐一注册serveStatic路由,/assets/*与/.well-known/*整体静态托管。注意/.well-known/*是静态优先、再兜底到 Start 的顺序。
  5. 兜底交给 TanStack Start:其余一切请求经startEntry.fetch(c.req.raw)进入dist/server/server.js,由 Start 完成 SSR / hydration / 文件路由。
  6. 错误兜底:onError输出 500 文本并带no-store。
  7. 监听配置:HOST环境变量默认0.0.0.0,PORT默认3000(PORT存在时强制数值化)。
const host = process.env.HOST ?? '0.0.0.0'; const port = process.env.PORT ? +process.env.PORT : 3000;

这一“外壳 + Start 内核”的分工在 docs/web/architecture-overview.md 的运行形态表中有明确描述:TanStack Start 负责文件路由、SSR、head/meta、loader 与 hydration;Node server 负责 Fly/Node 环境下托管静态资源、保留/health并把页面请求交给 Start。README 提到的 “Sitemap routes are handled by TanStack Start server routes” 也对应这里的第 5 步——/sitemap-index.xml、/sitemap-*.xml、/llms.txt、/openapi.json、/.well-known/mcp/server-card.json等都是 Start 文件路由(如 src/routes/llms[.]txt.ts)返回的 server-only handler,而非 Hono 外壳。

四、构建期配置:vite.config.ts 中的环境变量注入与插件链

vite.config.ts 是理解 Web 构建行为的关键文件,README 的三条命令最终都受它约束。

4.1 环境变量:带默认值的注入表

文件顶部声明了KNOWN_ENV并逐条用process.env[key] ?? value解析,形成最终生效值:

变量默认值用途
APP_HOSTanimes.garden站点主域名,用于 canonical、llms.txt等
FEED_HOSTapi.animes.garden公共 API / RSS 域名
WEB_SERVER_URLhttps://api.animes.garden/SSR 数据请求的 API base URL
KEEPSHARE_IDgv78k1oiKeepshare 前端标识
UMAMI_HOSTumami.animes.gardenUmami 统计来源
UMAMI_IDbcff225d-...Umami 站点 ID

这些值经unplugin-info/vite(Info插件)注入为~build/env虚拟模块,被 src/query/proxy.ts(APP_HOST、FEED_HOST、WEB_SERVER_URL)等模块消费;KEEPSHARE_ID用于 src/utils/keepshare.ts 同源的 Keepshare 集成。构建启动时还有一段animegarden-web:print内联插件,会打印 App/Feed/Server 三个地址,便于核对本次构建的目标环境。

4.2 插件链与 Agent Skills 发布

插件顺序为:Info→Analytics(Umami 埋点注入)→UnoCSS→tanstackStart()→viteReact→Icons→tsconfigPaths→Inline,外加一个自定义插件animegarden-web:agent-skills:

  • 该插件只在build且输出目录为dist/client时触发writeBundle,把仓库根目录 skills/animegarden/ 下的SKILL.md等文件复制到产物的.well-known/agent-skills/animegarden/,并解析 SKILL.md 头部的name/description生成带sha256:digest 的index.json发现清单。缺失name或description会直接让构建失败。
  • 从源码结构看,这套机制让 Web 静态产物自带面向 AI Agent 的技能发现端点,与首页Link头(AgentDiscoveryLinkHeader,见 src/utils/response.ts 指向/openapi.json、/sitemap-index.xml、/llms.txt)共同构成 LLM/Agent 发现链路。

五、部署链路:Dockerfile 与 fly.toml

README 的start:node在容器里的对应物是 Dockerfile 的最后两行。apps/web/Dockerfile 的要点:

  • 基础镜像为platformatic/node-caged:26.3.1-slim,并显式验证 Node 指针压缩(V8 pointer compression)开启后才继续构建;
  • 构建阶段通过NODE_ENV=development pnpm install --frozen-lockfile安装依赖,随后执行pnpm run build:web && pnpm prune --prod得到仅含生产依赖的产物;
  • ARG/ENV中携带与 4.1 相同的APP_HOST、FEED_HOST、WEB_SERVER_URL、UMAMI_*等默认值,可在镜像构建时覆盖;
  • 最终镜像CMD ["node", "apps/web/server.mjs"],EXPOSE 3000,时区固定TZ=Asia/Shanghai。

Fly.io 配置 则定义了线上拓扑:

  • [build]使用本目录 Dockerfile;[build.args]中WEB_SERVER_URL指向内网http://animegarden-server-production.flycast/,即 Fly 内网直连后端 server 应用(这一内外网分离的部署形态详见 docs/server/deployment-topology.md);
  • [http_service]监听internal_port = 3000,min_machines_running = 2,并发soft_limit = 4(requests 类型);
  • [[http_service.checks]]每 60s 对GET /health做健康探测,grace period 30s——这正是server.mjs保留/health路由的部署原因;
  • [[restart]]为on-failure重试 10 次;[[vm]]规格为 1g 内存 / 1 shared CPU。

六、请求中间件与缓存语义:src/start.ts 的三层 request middleware

进入 Start 内核后,src/start.ts 注册了三个 request middleware,与 README 所述“serves Start pages”的服务行为直接相关:

  1. CSRF:createCsrfMiddleware,仅作用于serverFn类型的 handler;
  2. ETag:对非 HTML 的router类型GET200 响应统一生成强 ETag(底层复用 server 包的 safeEtagResponse,基于 SHA-1 摘要,命中If-None-Match时返回 304 并保留cache-control/vary等头),HTML 页面与serverFn被排除以避免干扰 SSR streaming 与 RPC 序列化;
  3. Markdown 协商:GET/HEAD且Accept: text/markdown(且未显式q=0)时,对已登记页面路径(/、/anime、/calendar/YYYY-MM、/resources及其分页、/detail/:provider/:id、/subject/:id、/collection/:hash)在进入 HTML SSR 之前改由 src/markdown/ 返回应用侧拼装的 Markdown,使 Agent/LLM 与爬虫能以 Markdown 形式消费页面。

测试侧,test/start-etag.test.ts 直接调用startInstance.getOptions()取出中间件数组(断言长度为 3),对 ETag 生成、304 保留头、非覆盖范围(serverFn/HEAD/非 200/HTML)以及 markdown 与 sitemap 类响应逐一断言,是验证这一层行为的最直接依据。

页面级缓存头则由 src/utils/response.ts 统一收敛:列表类(timestamp、资源列表、collection)为public, max-age=30, s-maxage=60,详情/周历/文档类为public, max-age=3600, s-maxage=86400,上游失败时经setErrorResponse返回 502 并附Cache-Control: no-store,确保错误响应不被浏览器或 CDN 缓存。

七、路由与客户端装配:router.tsx 与 client.tsx

  • src/router.tsx 中getRouter()以routeTree(由routeTree.gen.ts生成)、createQueryClient()与createAppStores()构建 router,启用scrollRestoration: true、defaultPreload: 'intent'(hover 预载)与共享的PendingPage加载组件;QueryClient 默认staleTime为 1 分钟、gcTime为 10 分钟,与上面 4.6 的 30s 列表缓存形成“短缓存 + 预载保新鲜”的配合。parseRouterSearch/stringifyRouterSearch则保证 search 参数支持同名多值数组。
  • src/client.tsx 是 hydration 入口:startTransition包裹hydrateRoot(document, <StrictMode><StartClient /></StrictMode>),并引入~build/console虚拟模块。
  • 文件路由集中在 src/routes/(index.tsx、resources/、detail/$provider/$providerId/、subject/$subject/、collection/$hash/、calendar/$season.tsx、llms[.]txt.ts、openapi[.]json.ts、sitemap-index[.]xml.ts等),而页面纯展示组件放在 src/pages/;两者边界(route 负责 loader/head/canonical,page 只接收 props)在 docs/web/architecture-overview.md 的 “Route / Page 边界” 一节有完整约定,改动页面数据加载时应以src/routes/**为第一入口。

八、小结:从命令到线上的完整闭环

环节入口/文件关键行为
开发pnpm -C apps/web devVite + tanstackStart 插件,VIRTUAL_PWA_MODULE=true
构建pnpm -C apps/web build产出dist/client+dist/server/server.js,附带.well-known/agent-skills
运行pnpm -C apps/web start:nodenode server.mjs:Hono 外壳 + Start SSR 兜底
健康检查server.mjs→GET /health供 fly.toml 的 HTTP check 使用
API 边界server.mjs→/api/*404数据一律走@animegarden/client指向独立 server/feed
部署Dockerfile + fly.toml两阶段构建、内网flycast直连后端、2 台常驻机器

掌握以上内容后,你可以:在本地完整运行 AnimeGarden Web(dev→build→start:node),依据vite.config.ts与 Dockerfile 的ARG表按环境覆盖APP_HOST/FEED_HOST/WEB_SERVER_URL等变量,并基于src/start.ts的中间件与src/utils/response.ts的缓存枚举,理解其 ETag/Markdown 协商/缓存头策略的实现与测试方式。

  • 后端
  • 前端
  • 网页爬虫
  • MCP 服务
  • AI 技能

【免费下载链接】AnimeGarden

動漫花園 镜像站 | 动画 BT 资源聚合站 | 动画 BT 资源开放接口

项目地址:https://gitcode.com/gh_mirrors/an/AnimeGarden
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 16:38:55

「恋爱博士」流出,4k无常分享~

「恋爱博士」 链接&#xff1a;https://pan.baidu.com/s/16I-RI_XHuk8K3j33EdUk3A?pwd6666一句话&#xff0c;它讲的是什么&#xff0c;一个因骨癌失去一条腿的博士生&#xff0c;和一个在人生岔路口彻底迷路的女硕士&#xff0c;在一间机器人实验室里相遇&#xff0c;慢慢靠近…

作者头像 李华
网站建设 2026/10/14 16:38:54

Dynamic 356 OP 9.0组织导入org_code唯一性报错根因与四层加固

1. 问题现场还原&#xff1a;一个看似普通的DB导入&#xff0c;为何在同服务器环境下突然报错&#xff1f;“Dynamic 356 OP 9.0版本 同服务器环境还原DB导入组织报错”——这个标题不是某次线上事故的通报&#xff0c;而是我上周在协助某高校实验室部署教学管理后台时&#xf…

作者头像 李华
网站建设 2026/10/14 16:38:34

Go 后端转 AI:AI 功能怎么报价?一张 10 行自检表说清口径

上周有个人问我&#xff1a;我们这个 AI 客服上线一个月&#xff0c;大概要花多少钱。 我说你先告诉我三个数&#xff1a;一次对话平均调几次模型、每次输入多少 token、重试率多少。 他一个都答不上来。 这在我这儿不是第一次。能报出模型单价的人十有八九&#xff0c;能说出自…

作者头像 李华
网站建设 2026/10/14 16:38:02

工作流请假实例从建模到落地:节点、网关与避坑实践

简介&#xff1a;工作流引擎是企业系统自动化的核心组件&#xff0c;而“请假审批”是最典型的入门与实战场景。理解流程定义、节点与网关原理&#xff0c;能帮助开发者掌握状态流转、任务分配和条件分支等基础能力。在工程实践中&#xff0c;合理设计BPMN模型、配置排他网关与…

作者头像 李华
网站建设 2026/10/14 16:37:51

拒绝臃肿:国产开源免费项目管理工具Kanass实战指南

拒绝臃肿&#xff0c;推荐一款国产开源免费项目管理工具 - Kanass做项目这行久了&#xff0c;对“项目管理工具”这五个字真是又爱又恨。爱的是它确实能把散落的需求、进度、人凑到一块儿&#xff0c;恨的是市面上主流的那些工具越来越像一个贪吃蛇——功能多得让人找不到入口&…

作者头像 李华