用途
Frontend SEO 描述一套 SEO 系统,而非组件库或视觉风格。它由一组纯构建函数加一层轻量框架适配器组成,目标是让每个路由都能输出正确、一致、机器可读的元数据,避免任何人复制粘贴 <meta> 标签。站点身份信息集中在单一 constants 模块中,URL 始终为绝对规范形式,搜索引擎可获取由应用已渲染内容派生的 sitemap、robots 规则、RSS feed 和类型化 JSON-LD。
该技能与 frontend-architecture 技能配套:SEO 系统位于单一服务模块(services/seo/)中,并通过一个 barrel 消费。它可被 Claude Code、OpenCode、Codex、Cursor、Windsurf 等工具读取。
五个核心思想
- 身份信息的单一事实来源:站点 URL、名称、描述、关键词、作者、社交账号、OG 图片和验证令牌位于单一 constants/seo 模块,站点身份不在其他地方硬编码。
- URL 始终绝对且规范:单个 canonicalUrl(path) 函数将任意路径转为绝对、尾斜杠规范化的 URL,每条 sitemap 条目、RSS 链接、OG URL 和 JSON-LD @id 都经由它生成。
- 构建函数为纯函数,适配器很薄:元数据、sitemap、robots、RSS 和 JSON-LD 都由接收数据并返回普通对象的纯函数生成,只有一个很小的函数接触框架的元数据类型,纯函数易于单元测试。
- 结构化数据有类型且可复用:JSON-LD 对象共享 JsonLd 类型和一组小型 schema.org 构建器(Person、WebSite、BlogPosting、CreativeWork、BreadcrumbList、FAQPage),实体之间通过稳定的 @id 交叉引用。
- 发现面由内容生成:sitemap.xml、robots.txt 和 RSS feed 由应用渲染的同一内容集合构建,绝不手工维护,绝不漂移。
目录布局
SEO 系统是一个服务模块加上其常量和类型,直接嵌入 frontend-architecture 的形态(shared/ 或 services/)中。
src/
├── constants/
│ └── seo.ts ← 站点身份的唯一事实来源
├── types/
│ └── seo.ts ← SchemaType, RouteDescriptor, SitemapEntry,
│ RobotsConfig, RssItem, Redirect, JsonLd
├── services/seo/
│ ├── index.ts ← barrel: canonicalUrl, buildMetadata,
│ │ sitemapEntries, robots, rssItems,
│ │ structuredData, redirects
│ └── structured-data.ts ← 按类型的 JSON-LD 构建器(Person, WebSite, …)
└── app/ (or routes/) ← 薄适配器:路由文件调用构建器
├── layout.tsx ← 全局默认元数据(来自 constants/seo)
├── sitemap.ts ← 挂载 sitemapEntries()
├── robots.ts ← 挂载 robots()
└── feed.xml/route.ts ← 挂载 rssItems()
经验法则:构建器从不导入框架(唯一例外是 buildMetadata 适配器);路由文件从不内联构建 SEO 数据,而是调用构建器并挂载结果。
身份信息的单一事实来源
站点身份的每一项都是具名常量,避免路由文件中散落的裸字符串、描述的第二份副本或硬编码的 base URL。示例:SITE_URL、SITE_NAME、SITE_HANDLE、SITE_LOCALE、SITE_TITLE_DEFAULT、SITE_TITLE_TEMPLATE(子页面填充 %s)、SITE_DESCRIPTION、SITE_KEYWORDS、AUTHOR_NAME、AUTHOR_EMAIL、AUTHOR_GITHUB、AUTHOR_LINKEDIN、OG_IMAGE_PATH、OG_IMAGE_WIDTH、OG_IMAGE_HEIGHT、GOOGLE_SITE_VERIFICATION。这样修改描述或 OG 图片只需改一行,结构化数据、OG 标签和 Twitter card 读取相同值,不会互相矛盾。
类型化数据模型
types/seo.ts 定义每个构建器遵循的契约:SchemaType 为 Person、WebSite、BlogPosting、CreativeWork、BreadcrumbList、FAQPage 的联合类型;RouteDescriptor 描述路由(path、title、description、ogImage?、indexable?);SitemapEntry(url、lastModified?、changeFrequency?、priority?);RobotsConfig(rules、sitemap);RssItem(title、link、description、pubDate、guid);Redirect(source、destination、permanent);JsonLd(@context、@type 及类型专属字段)。frontend-architecture 的 I 前缀约定适用于有状态 UI 接口;这些 SEO 数据模型是普通 DTO,遵循宿主项目已有约定即可。
规范 URL
canonicalUrl(path) 是系统的脊柱,保证绝对、规范化、无双斜杠的 URL,使搜索引擎不会为同一页面看到两个 URL。硬性规则:绝不手工拼接 SITE_URL + path;选定一种尾斜杠策略(该技能为无尾斜杠)并处处应用;每个 OG url、sitemap url、RSS link 和 JSON-LD @id/url 都经由 canonicalUrl。
每路由元数据
buildMetadata 是唯一允许了解框架元数据类型的函数,其余均与框架无关。全局默认值在根 layout 中设置一次(title 模板、默认 OG/Twitter card、robots 策略、图标、manifest、验证),子路由只覆盖有差异的部分。动态页面(如 app/blog/[slug]/page.tsx)通过 generateMetadata 读取实体并返回路由专属元数据,标题模板自动填充 %s。硬性规则:默认值只在 layout 设置一次;使用标题模板避免子页面重复站点名;每个页面解析单一 canonical,绝不输出重复或相对 canonical。
发现面(由内容生成)
sitemap:从应用渲染的同一内容集合构建条目,去重且全部绝对。此技能提供 PRIMARY_ROUTES 数组与 sitemapEntries({blogSlugs, projectSlugs}) 函数,再由 app/sitemap.ts 薄适配器映射为框架类型。
robots:app/robots.ts 返回规则,始终 disallow 私有区域(/api/、/admin/、构建内部)并指向 sitemap。
RSS:rssItems(posts) 生成 RssItem 数组,app/feed.xml/route.ts 按最新优先排序并用 CDATA 包裹自由文本,使撇号和标记不会破坏 feed。
结构化数据
structuredData(type, data) 为通用构建器,返回带 @context 和 @type 的 JSON-LD。按类型构建器(personJsonLd、websiteJsonLd、blogPostingJsonLd 等)通过稳定 @id 交叉引用,例如 WebSite 的 author 引用 #person 而非复制。提供的构建器覆盖 Person、WebSite、BlogPosting、CreativeWork、BreadcrumbList、FAQPage。
适用场景与限制
适用于 React 或 React Native-for-web 前端,尤其是希望所有路由输出一致、机器可读元数据并自动生成发现面内容的项目。该技能本身是方法描述,不含现成 npm 包、价格、平台支持范围或运行效果保证;具体框架适配(示例多为 Next.js)需按宿主项目调整。资料未提供的内容均暂未提供。