- 后端
- 前端
- 网页爬虫
- MCP 服务
- AI 技能
【免费下载链接】AnimeGarden
動漫花園 镜像站 | 动画 BT 资源聚合站 | 动画 BT 资源开放接口
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字段,可以看到每条命令的真实形态与适用场景:
| 命令 | 实际执行 | 说明 |
|---|---|---|
dev | VIRTUAL_PWA_MODULE=true vite dev | Vite 开发服务器 + TanStack Start 开发模式 |
build | VIRTUAL_PWA_MODULE=true vite build | 产出dist/client(静态资源)与dist/server/server.js(SSR 入口) |
start:node/preview | node server.mjs | 以 Hono Node 外壳托管生产产物并监听端口 |
test/test:ci | vitest/vitest --run | 前端测试(如 test/start-etag.test.ts) |
typecheck | tsc | TypeScript 检查 |
几个值得展开的细节:
VIRTUAL_PWA_MODULE=true。dev与build都显式设置了这个环境变量,用于在本地开发时以虚拟模块方式屏蔽 PWA 构建依赖,保证不依赖完整 PWA 产物也能完成构建流程。- 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。 - 构建目标与模块解析。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 完整展开,它的请求处理顺序是:
- 请求日志:对所有路由挂
hono/logger,经consola带Web标签输出。 /health健康检查:GET/任意方法均返回{ "status": "OK" },并带 CORS。这是 Fly 平台存活探测的端点(见第五节 fly.toml 的checks配置)。/api/*显式 404:如第一节所述,与独立 API/feed 服务的边界在入口层就被强制。- 静态资源:优先探测
.output/public/,否则使用dist/client/作为ClientRoot;根目录下的每个文件(favicon、site.webmanifest、twitter.jpg等 public/ 中的文件)逐一注册serveStatic路由,/assets/*与/.well-known/*整体静态托管。注意/.well-known/*是静态优先、再兜底到 Start 的顺序。 - 兜底交给 TanStack Start:其余一切请求经
startEntry.fetch(c.req.raw)进入dist/server/server.js,由 Start 完成 SSR / hydration / 文件路由。 - 错误兜底:
onError输出 500 文本并带no-store。 - 监听配置:
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_HOST | animes.garden | 站点主域名,用于 canonical、llms.txt等 |
FEED_HOST | api.animes.garden | 公共 API / RSS 域名 |
WEB_SERVER_URL | https://api.animes.garden/ | SSR 数据请求的 API base URL |
KEEPSHARE_ID | gv78k1oi | Keepshare 前端标识 |
UMAMI_HOST | umami.animes.garden | Umami 统计来源 |
UMAMI_ID | bcff225d-... | 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”的服务行为直接相关:
- CSRF:
createCsrfMiddleware,仅作用于serverFn类型的 handler; - ETag:对非 HTML 的
router类型GET200 响应统一生成强 ETag(底层复用 server 包的 safeEtagResponse,基于 SHA-1 摘要,命中If-None-Match时返回 304 并保留cache-control/vary等头),HTML 页面与serverFn被排除以避免干扰 SSR streaming 与 RPC 序列化; - 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 dev | Vite + 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:node | node 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 资源开放接口
相关推荐
AnimeGarden Web 架构深度解析:TanStack Start + Hono 双壳的 SSR 前端设计与数据流
AnimeGarden Web 架构深度解析:TanStack Start + Hono 双壳的 SSR 前端设计与数据流 apps/web 是 AnimeGa
后端前端网页爬虫MCP 服务AI 技能AnimeGarden Web 前端架构与工程实践指南:TanStack Start 路由、SEO、埋点与迁移对齐
AnimeGarden Web 前端架构与工程实践指南:TanStack Start 路由、SEO、埋点与迁移对齐 导读 本文以 apps/web (Anime
后端前端网页爬虫MCP 服务AI 技能OpenPanel 前端仪表盘(apps/start)开发指南:基于 TanStack Start 的构建、运行与工程化实践
OpenPanel 前端仪表盘(apps/start)开发指南:基于 TanStack Start 的构建、运行与工程化实践 本篇技术指南围绕 OpenPane
数据可视化后端前端MCP 服务
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考