跳转到正文

渲染约定

这是每种前端技术栈的 <head> 渲染 Rankbeam SEO 数据时都必须满足的唯一权威检查清单,也是以下内容的可信依据:

  • 核心中的渲染器数据结构单元测试,即 tests/Unit/Services/RenderingContractTest.php,属于包 CI 覆盖的快速、无框架测试部分。
  • rankbeam-examples 中各技术栈的参考应用,包括 Blade、Inertia + Vue / React / Svelte、Livewire。其浏览器和 SSR 测试在真实 DOM 中验证相同断言。
  • Blade、Inertia 与 JSON、Livewire 等框架指南,不得提供违反约定的实现方式。

如果某个技术栈无法满足条款,应视为缺陷或有文档说明的限制,不能以此为由削弱约定。数据层,即 SEOResolver → 不可变的 SEODataTagRenderer,与框架无关。不同技术栈的区别仅在于解析后的数据如何进入 DOM、在客户端导航后正确保留,并且仍可被爬虫读取,而这正是本约定固定的内容。

本规范曾经过独立设计审查并据此加固。 只有发生实质变更时,才需要重新审查。


1. 值:符合约定的 <head> 包含什么

标题、描述和规范网址

  • 恰好一个 <title>,内容为解析后的标题,不得重复追加后缀。解析器仅追加一次 seo.title_suffix,并检查标题是否已以该后缀结尾。
  • 一个元描述,仅在解析出描述时输出,不生成空标签。
  • 一个 <link rel="canonical">

Robots

  • 仅当指令不同于网站默认值时输出 <meta name="robots">。多余的 index,follow 只会增加噪声;没有该标签时,爬虫本来就按 index,follow 处理。比较时忽略空白差异,index, followindex,follow;与默认值不同的指令则原样输出seo.robots.emit_default = true 会强制输出标签。
  • 支持结果确定的高级指令noindexnofollownoarchivenosnippetmax-snippetmax-image-previewmax-video-previewnotranslateunavailable_after。它们是解析后的字符串值,优先级由解析器链决定,即全局 → 路由 → 模型 → 显式值。输入相同,输出就相同。

Open Graph

  • og:titleog:descriptionog:typeog:urlog:site_nameog:locale
  • article:*,包括 published_timemodified_timeauthorsectiontag仅在 og:type === 'article' 且值真实存在时输出;绝不编造,也不在非文章页面上输出。
  • og:image 附带已知的 og:image:width / og:image:height / og:image:altog:image:type。多张图片需要分组:每个 og:image 后面立即跟随该图片自己的尺寸、alt 和类型属性。

Twitter Cards

  • twitter:cardtwitter:titletwitter:descriptiontwitter:image,以及已知图片替代文本时的 twitter:image:alt
  • twitter:sitetwitter:creator 可选且相互独立,可以只有其中一个,也不能从其中一个编造另一个。

hreflang 与语言

  • Hreflang 通过模型的 getSEOAlternates() 钩子获得正式的解析器支持。
  • 有 hreflang 替代版本时,网址必须绝对、规范化,并按语言唯一;数据完整时应双向关联。只有配置了 x-default 才输出它。
  • og:locale:alternate 反映真实存在社交版本的语言。将 en-US 映射为 en_US,按映射后的形式比较,不要求字面完全相等。
  • <html lang> 与解析后的语言保持一致。虽然 <html> 元素由应用输出,这一条仍属于约定。

逐页 JSON-LD

  • 可以解析,并对 </script> 安全。数据载荷使用 JSON_HEX_TAG 编码,防止任何值提前终止 script 元素,这是存储型 XSS 防护。
  • 多个 <script> 块或一个合并的 @graph 都可以接受。
  • 仅在实体实际相互关联时使用稳定的 @id,例如 Organization ↔ WebSite ↔ WebPage;独立节点不强制要求稳定的 @id

2. 规范化与不变量

  • canonicalog:urlog:imagetwitter:image 使用绝对 http(s) 网址空值或 null 标签不得进入 DOM
  • canonicalog:url 必须解析为相同的规范化网址。 不一致属于硬性失败,不是警告。
  • 规范网址的规范化策略在所有输出中保持一致:协议、主机、端口、路径大小写、查询参数允许列表和尾斜杠,每次都以相同方式处理。允许索引的页面引用自身noindex 页面不继承其他页面的规范网址策略。
  • 按输出上下文转义:HTML 属性、文本和 JSON 分别使用正确编码器。断言比较的是解码后的语义值,不是原始字节
  • 不同渲染器之间要求语义一致,不要求逐字节一致。 render()(HTML)≡ toArray()toInertiaHead(),在规范化后成立;三种表示形式的标签顺序和形式可以合理不同。单实例与可重复属性的规则必须明确,例如一个 og:title,多个 article:tag
  • 标签归属:客户端渲染器替换包拥有的标签,通过键识别,见第 4 节,不删除无关的应用自有标签。

3. 行为:客户端导航

每次 Inertia 访问或 Livewire wire:navigate 后:

  • 每种单实例标签恰好一个,包括 <title>、描述、规范链接及各个 og:*/twitter:*没有陈旧值
  • JSON-LD 不会累积。移除前一个页面的结构化数据,而不是继续叠加。Livewire 将 <script> 视为不可移除资源,因此结构化数据脚本带有 data-seo-schema 和逐网址 ID,并在 livewire:navigated 时移除前页脚本,详见 Livewire 指南。
  • 元数据丰富的页面导航到简单页面时,移除多余标签,后者不保留前者的描述、OG 或结构化数据。
  • 没有水合警告,并且水合前后元数据语义一致。

4. Inertia head-key:标签归属

toInertiaHead() 为每个 meta/link 条目添加稳定的 head-key。Inertia 按这个属性对 head 元素去重:页面中的 <Head> 标签如果与布局标签使用相同的 head-key,会替换它,而不是叠加一个重复项。

  • 基础键:meta 使用 name ?? property,link 使用 rel
  • 为可重复标签消除歧义,确保每个键仍然唯一:article:tagarticle:tagarticle:tag:1 等;hreflang → alternate:en-USalternate:fr-FR

在模板中绑定为 :head-key不要使用 Vue 的 :key。后者是用于 v-for 协调的另一个键,对 Inertia 的 head 去重不起作用。


5. 爬虫可见性:明确区分模式

  • SSR / 预渲染必须在原始 HTTP HTML 中输出完整约定要求的内容。这项测试与水合后 DOM 的测试分开进行,并禁用 JavaScript。
  • 仅 CSR 不能声称满足爬虫要求。 默认不使用 SSR 的 Inertia 在客户端注入元数据,爬虫取得的初始 HTML 没有 SEO 元数据。必须公开说明这个限制:爬虫可见的元数据需要 Inertia SSR 或预渲染,供爬虫读取的 JSON-LD 也应在服务器端渲染。

6. 范围之外与非目标

  • 属于应用而非渲染器的职责charsetviewport、favicon。注意,<meta charset> 必须先于任何非 ASCII 元数据,因此这些 head 元素的顺序由应用负责。
  • 端到端测试只断言输出内容。断言 Google 是否索引、选择哪个规范版本、是否符合富媒体搜索结果资格或排名,也断言远程图片的 MIME 类型或可用性。这些属于可选集成或 HTTP 测试,不属于浏览器矩阵。

7. 符合情况

下面说明目前每条约定由什么证据支持。单元测试RenderingContractTest,位于核心和包 CI 中。浏览器/SSRrankbeam-examples,通过定时矩阵运行。应用表示由宿主应用负责。计划中表示已作为目标写入约定,但 SEOData 尚未建模相应数据,因此渲染器只输出安全的子集。

条款状态
恰好一个解析后的 <title>,无重复后缀单元测试 + 浏览器
仅在描述存在时输出元描述单元测试 + 浏览器
一个 <link rel="canonical">,永不为空单元测试 + 浏览器
Robots 仅在不同于默认值时原样输出;emit_default 开关单元测试 + 浏览器
高级 robots 指令遵循解析器优先级单元测试,解析器
og:title/description/type/url/site_name/locale;语言 en-USen_US单元测试 + 浏览器
article:* 仅在 og:type=article 且值真实时输出单元测试 + 浏览器
og:image 存在且为绝对网址单元测试 + 浏览器
og:image:width/height/altog:image:type、多图片分组计划中SEOData 仅携带单个 ogImage 字符串,尚未建模尺寸、alt 或类型。渲染器输出一个绝对 og:image
twitter:card/title/description/imagesite/creator 相互独立单元测试 + 浏览器
twitter:image:alt计划中:尚未建模图片 alt 字段。
hreflang 为绝对网址,并按语言唯一单元测试 + 浏览器
hreflang 双向关联,以及配置时输出 x-default浏览器,取决于数据
og:locale:alternate 反映真实社交版本计划中:尚未建模逐语言社交版本映射。
<html lang> 一致性应用,浏览器也对其断言
JSON-LD 可解析并对 </script> 安全单元测试 + 浏览器
多脚本或 @graph;实体关联处使用稳定 @id单元测试,Merchant 数据图 + 浏览器
绝对网址;没有空或 null 标签单元测试 + 浏览器
canonicalog:url,不一致时硬性失败单元测试 + 浏览器
一致的规范网址规范化、自引用、noindex 隔离浏览器
按上下文转义;解码后的语义一致单元测试
不同渲染器语义一致,render()toArray()toInertiaHead()单元测试
Inertia head-key 稳定,可重复项无歧义单元测试 + 浏览器
客户端导航:单实例、无陈旧值、JSON-LD 不累积、清除多余标签浏览器;渲染器提供清理所需的 data-seo-schema 钩子
没有水合警告;水合前后一致浏览器
SSR 在原始 HTML 中输出完整约定内容;文档明确仅 CSR 不符合要求浏览器 + 文档

计划中的条款是有意记录、明确说明的缺口。约定是长期目标,这些内容作为未来任务中的新增、向后兼容扩展,需要新的 SEOData 字段或数据列,属于 SemVer 次版本变更。当前渲染器输出安全子集,绝不编造没有的数据。

rankbeam/laravel-seo 采用 MIT 许可证发布。