SEO、Sitemap 与 Robots
Vext 把 SEO 作为全栈应用的框架能力提供。它会把应用默认值、路由元数据和单次渲染元数据合并进服务端输出的 document,并可在构建期生成或在运行时提供 sitemap.xml 与 robots.txt。
在全栈项目中先添加页面元数据,再根据部署需要启用 sitemap/robots。全局 frontend.seo 未配置时,显式 route/render SEO 仍可生成 title 等元数据;完全没有这些声明才保持原有行为。显式 frontend.seo.enabled: false 可关闭结构化 SEO,legacy head 仍独立生效。
基础配置
将以下配置合并到 src/config/default.ts,先用固定 /about 条目验证完整流程,无需外部内容服务:
publicOrigin 表示部署 origin,不是把全站固定成同一个页面 URL。Vext 会把它与当前请求 pathname 组合,因此 /posts/hello 和 /posts/release-notes 会生成不同 canonical,即使它们共用一个 publicOrigin。
预览、测试和生产使用不同域名时,可由各环境设置 PUBLIC_ORIGIN。它必须是绝对 HTTP(S) URL,不能包含用户信息、query 或 hash。
页面级元数据
静态且 JSON-safe 的元数据放在既有路由声明上。有限静态语法以内联对象为最简单形式,也接受同文件 const 绑定与 TypeScript 静态包装。route options helper 调用会被拒绝,因为索引不会执行 helper 函数体,无法确认最终元数据。请内联最终对象,或直接传入保存最终对象的同文件 const。索引也不会执行导入值、计算表达式或带插值的模板字符串:
运行 npm run build,在默认 dist/client/sitemap.xml 确认含 https://www.example.com/about,robots.txt 含 sitemap URL。启动 npm start -- --port 3000 后请求 /about,原始 HTML 应包含“关于我们 | Example”标题、canonical、description 与 og:type=profile;因为本例 hydration 为 none,不应有 Vext hydration 数据/浏览器入口。页面正文应可见。HTTP 访问 /sitemap.xml 与 /robots.txt 应200且内容正确,验证后停止服务。
部署 manifest 与内置前端静态服务共用 MIME 表,XML 返回 application/xml; charset=utf-8,TXT 返回 text/plain; charset=utf-8。外部静态托管/CDN 也须保持这些类型;验收实际 HTTP Content-Type 与文件内容。
示例 origin 是输出地址,不要求本地请求连接该域名;部署时换成真实 origin。build 模式静态文件不做 runtime Host 选择。不要仅看到 HTML 里存在 title 就推定搜索引擎已经收录。
依赖业务数据的元数据放在 res.render() 第三个参数中。下面是注册回调内的集成片段,前提是已有 posts service、对应类型扩展及 posts/detail 页面,并对不存在的 post 返回404;它不属于上面可直接运行的 about 示例:
合并顺序是 frontend.seo.defaults → RouteOptions.frontend.seo → res.render(..., { seo })。canonical、alternate 和相对 Open Graph URL 需要已声明的 origin。canonical 与 sitemap 使用绝对 pathname;query 和 hash 会被拒绝,避免误生成重复 URL。
支持 title、description、robots 指令、canonical、Open Graph、Twitter card、多语言 alternate 和 JSON-LD。既有 res.render(..., { head }) 继续兼容;显式 legacy head 字段在结构化 SEO 之后合并。
构建期 Sitemap
sitemap: {} 默认使用 build 模式。成功生成的静态页面会自动进入 sitemap,entries provider 可补充构建期已知的动态路径:
build 模式必须配置 publicOrigin。产物会写入前端构建 closure,并以正确的 XML/TXT MIME 进入 deploy manifest。设置 frontend.seo.index: false 或 robots 包含 noindex 的静态路由不会进入 sitemap。URL 数量超过 maxUrlsPerFile 时会生成 sitemap index 与编号分片;默认上限为 50,000。
完整 sitemap 集合还有独立预算:maxUrls 默认 100,000,maxBytes 默认 50 MiB 渲染后 UTF-8 输出,运行时 timeoutMs 默认 5,000 ms。任一预算超限时生成会立即停止并 fail closed;运行时期限会中止 provider signal。
provider 只接收 { mode, origin, originKey, signal };Vext 不会向配置回调注入 app、services 或 app.db。动态条目应来自构建期安全模块或外部内容源,并正确响应 abort signal。
这些 seo: { ... } 片段合并到已启用的 frontend.seo 中。CMS 地址是需要替换的集成示意,真实数据应经过应用校验;provider 返回的是条目,不会替你创建对应路由。includeStatic 自动收集成功静态页面,不表示扫描所有 SSR 路由。构建 provider 应自行管理外部 I/O 的超时,timeoutMs 专用于 runtime 期限。
运行时 Sitemap 与动态域名
条目或公开域名需要按请求选择时使用 runtime 模式:
运行时 sitemap/robots endpoint 的 Host 必须精确匹配 publicOrigin 或 origins 中的一项,未知 host 返回404。此规则不表示所有普通页面都会因未知 Host 返回404:页面 canonical 来自已配置的 publicOrigin 或 seo.originKey,不会直接信任请求 Host。路由或单次 render 可通过 seo.originKey 选择有限命名 origin;未声明 key 会报错。
配置的 origin 会按 trailing dot 与默认端口等规则规范化后比较 host,同时保留 publicOrigin 中的 pathname base,并用于 canonical、sitemap index、分片和 robots URL。运行时 SEO endpoint 同时支持 GET 与 HEAD;HEAD 返回相同 status 与 headers,但不输出实体 body。
runtime sitemap 与 robots 响应使用 Cache-Control: no-store。只有在明确 host 和刷新策略后,才应在反向代理增加缓存。
本地验证 runtime 模式时,向本地端口发请求并显式发送已声明 Host,例如 curl -i -H "Host: www.example.com" http://127.0.0.1:3000/sitemap.xml;换成未声明 Host 应404,curl -I 应有相同状态/头但无 body。使用基础 build 配置不会获得这些 runtime 行为。
Robots
robots 路径固定为 /robots.txt。启用 sitemap 时,生成的 robots 文件也会写入 sitemap URL。runtime SEO endpoint 若与用户已有 GET 或 HEAD 路由冲突,应用会在启动时失败并给出冲突说明。
无浏览器 Hydration 的 SEO
SEO 在 document policy 之前应用,因此 frontend.hydration: "none" 路由仍保留 SSR HTML、CSS、canonical、Open Graph、JSON-LD 与用户写在 document 中的 script,同时省略 Vext/React 浏览器 runtime。精确边界见 Hydration。
这是页面级 server-only 渲染,不等于 Selective 或 Partial Hydration、Islands 架构、React Server Components 或 Partial Prerendering(PPR)。