如果一个页面已经能正常打开,SEO 算做好了吗?
这次重做主页时,我依旧不会给 SEO 单独排一个阶段。URL、结构化数据、可抓取性这些东西,从一开始就跟着架构一起定了——因为它们不是上线前再补几项标签,而是贯穿整个站点的横切约束。
这篇把技术篇《重做个人主页:建站的技术决策》里一笔带过的 SEO 单独拆出来,按七条验收标准一条条过:标题描述、URL 写法、标题层级、语义化标签、随开关收敛的导航元素、JSON-LD 转义、禁用 JS 可读;最后补外链、社交分享与 llms 三个扩展项。
验收标准是先从经验里定的,一共七条:
- 页面标题描述不散落;
- URL 写法一致;
- 一个页面只有一个 H1,层级整页连续;
- 标签按含义出场(键值对、图片说明);
- 关闭板块,导航元素残留;
- 结构化数据不乱转义;
- 禁用 JS 也能读完。
其中四条很多站点都会犯:标题描述漂移、尾斜杠写法分叉、关闭板块导航元素残留、禁用 JS 后整页空白。剩下一条不是错,是规则变了——2026 年 Google 收紧 JSON-LD 转义的解析要求,不再替网站修正格式问题。另有两条从一开始就靠组件分工定型(H1 唯一性、语义化标签),新页面也难写歪。
这七条里,六条与内容规模无关——第 2 到 7 条,从第一页起就该正确;它们不是“基建选项”,是“基本正确”,别按“以后再说”处理。剩第 1 条与规模挂钩:每个页面的标题、描述这类文字,页面多了单独写会风格漂移、容易漏,才值得收进一套配置统一生成——判断标准是同一种写法出现第二次;站只有几页,直接写更快。
TDK 配置化:标签集中,页面只声明差异
标题、描述、关键词(缩写 TDK)这类标签最容易被每页写一遍,然后风格漂移。我的做法:按页面类型归一组配置,页面模板只传差异部分(标题不含站点名,布局层自动拼接),站点名和默认描述集中在全站配置;页面不传时回退全站默认——新页面不加 TDK 也有兜底,不会出现裸 title。
落地的样子大致是这样:
// 一处定义,页面引用
const seo = {
weekly: { title: '拾趣周刊', description: '每周精选…' },
photos: { title: '影集', description: '摄影与视觉记录' },
};
// 页面传 title 即可,布局层自动补「- 站点名」和默认描述
关键词(K)没在示例里,是有原因的:Google 早就明确不把 keywords 当排名因素,现代站点多数不维护它。我在配置里保留字段是为了站内管理与老习惯——不做国际 SEO 的站,直接不维护这一项就是。三件套里,真正值得配置化的是标题和描述。
URL 是契约:尾斜杠必须统一
搜索引擎把带不带尾斜杠看成两个 URL。canonical(告诉搜索引擎这个页面的正式地址)、给机器读的结构化数据、sitemap 三处写法不一致,等于声明了三个地址。做法:布局层统一规范——站点根 URL 和当前页面 URL 都剥掉尾斜杠再拼,canonical、结构化数据、分页 prev/next 全走同一份规范化结果,一次统一后“同一个 URL 两个写法”从根上消失。
落地:全站只有这一个函数管地址写法,一处改,各处跟着对:
const normalize = (url: string) => url.replace(/\/?$/, '/');
// canonical、结构化数据、sitemap 全用它的结果
标题层级:一个页面只有一个 H1
机器读一个页面,先看的是骨架而不是正文——标题层级就是骨架。规矩两条,都以整个页面为单位:一个页面只能有一个 H1(一级标题,这一页最大的标题),小节按 H2、H3 往下排、整页连续不跳级。
打个比方:H1 是书名,H2 是章,H3 是节,没有 H3 直接出 H4,等于一本书缺了页。不同页面,H1 承载的东西不同:首页是品牌名(这是谁的主页),列表页是板块名(这里是周刊),详情页是文章标题,功能页是页面名称——规则不变:一页一个 H1。
<h1>周刊标题</h1> <!-- 详情页的页面主体标题,全页唯一 -->
<h2>正文第一节</h2>
<h3>节内小标题</h3>
容易翻车的地方:页面顶部有个标题(没错),正文第一句又写一个 H1(错了)——一页两个 H1,机器不知道谁是主体。所以正文从 ##(H2)起写,这是页面规则在详情页的落实:文章标题已经占了页面的 H1,正文再写 H1 就撞了,它从来不是孤立于正文的约束。
落地靠组件不靠手写:页面主体标题由统一的页头组件输出 H1、板块头组件输出 H2、卡片标题固定 H3——“一页一个 H1”被组件锁住,新页面写不出第二个,也跳不了级。
检查一步:查看页面源代码,搜 <h1——这一个页面里只有一个,顺着往下数 h2、h3,不跳级。
代码语义化:标签按含义出场,样式交给 class
语义化的收益长期、微小,不必完美主义,但三种情况值得做对——它们影响机器对页面信息的理解:键值对、无意义强调、图片说明。
- 键值对用 dl/dt/dd(定义列表:一个名字配一个值)。“类型: 个人主页 / 站长: MoeWah”是字段与值的关系,用定义列表包起来,机器才分得清哪是名字哪是值;写成普通文本能看,但字段关系丢了。
<dl>
<dt>类型</dt><dd>个人主页</dd>
<dt>站长</dt><dd>MoeWah</dd>
</dl>
-
视觉强调不用
<b>。<b>是无含义的样式标签;品牌名中间拆出粗体这种诉求,用 span 加 CSS 字重,语义干净。 -
图片加说明用 figure/figcaption(图片与说明的组合标签)。正文图片配一行说明,是“图片与其说明”的关系,金标准是
<figure>包住<img>和说明文字。
这几处替换只换标签不动 class——视觉由 CSS 决定,语义由标签决定,两件事分开。所以改语义不伤样式,这是敢改的原因。
检查一步:字段信息是不是 dl 包起来的、图片说明是不是 figure、有没有拿 b 当强调。
结构化数据出在布局层
JSON-LD(给搜索引擎的结构化描述)不该每页自己写。布局层统一注入站点身份(网站、作者、网页)三件套,页面只声明自己的类型(比如文章页声明 Article),面包屑自动生成。两个要点:
- 写进结构化数据的内容要与页面实际显示一致——页面没写的东西不要写进给机器读的数据,不为实体丰富度硬凑。
- 关掉一个板块,它也从结构化数据里消失——否则导航元素(SiteNavigationElement,声明网站导航入口的结构化数据)会向搜索引擎宣告一个不存在的页面。对应过一次 bug:它里面一度残留已禁用板块,修掉后养成了“加开关必查结构化数据”的习惯。
落地就一行:布局层注入,页面只说自己是谁。
<script type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
<!-- 布局层注入一次,页面只声明自己的类型 -->
JSON-LD 转义:序列化一次,别再整体转义
2026 年 Google 调整了对 JSON-LD 的提取方式:Googlebot 现在只做一次 HTML 反转义,不再替格式有问题的内容做修正。后果:属性值被解析成字面量 &、结构化数据与页面实际内容不一致、富摘要(搜索结果里的卡片展示)无法触发。这不是算法更新,是解析器对格式合规的要求变严格了。
处理原则:先生成标准 JSON,再安全嵌入 HTML;序列化只做一次。先建对象,JSON.stringify 一次,直接进 script 标签;不要再对序列化结果做整体 HTML entity 转义,更不要重复序列化。
落地三步,到头了:
const safeJson = JSON.stringify(data)
.replace(/</g, '\\u003c')
.replace(/>/g, '\\u003e')
.replace(/&/g, '\\u0026');
// 建对象 → 只转义这一次 → 直接进 <script type="application/ld+json">
正文来自外部接口时,真正要防的是内容里的 </script> 打断标签:用 JSON Unicode 转义(< → \u003c),而不是 HTML entity(<)。普通 & 是合法 JSON 字符不一定要转;内容经过 HTML 模板链时统一 \u0026 更稳。
重做主页时外部内容进结构化数据的处理链:渲染后 HTML 先还原纯文本 → 一次 JSON.stringify → < 转 \u003c → 构建产物逐条核对与页面可见正文一致。验证方式:看页面源代码(不是开发者工具处理后的 DOM),提取 script[type="application/ld+json"] 直接 JSON.parse(),确认没有 &、< 残留。
JS 缺席时也要可读
可抓取性有个容易翻车的默认项:页面内容可见性不能依赖 JavaScript。重做时翻过一次车:入场动画默认把内容设成透明、等 JS 触发才显示——JS 不跑,内容明明写在页面里却看不出来,爬虫抓到的就是空壳。修复:内容默认可见,JS 启用后才追加动画类。动画是增强,不是访问前提。
检查方法很简单:浏览器禁用 JavaScript 打开页面,正文、图片、链接都要在。这比结构化数据重要——结构化数据是给机器补充描述,正文是机器和人共同的地基。
落地的模式就两行 CSS,动画是追加的,不是前提:
.reveal { opacity: 1; } /* 默认可见:禁用 JS 也能读全文 */
html.js .reveal { opacity: 0; } /* JS 开启后才先隐藏,等它来播放 */
外链 rel:信任信号也算 SEO
外链的 rel(链接属性,向搜索引擎声明这个链接是什么性质)属于站外 SEO——Google 官方把 nofollow、sponsored、ugc 列为链接信任信号。适用条件:三类链接同时存在的站(正文引用外站、赞助联盟链接、评论区用户链接);只有一类的话,写 Markdown 顺手带 rel 就够了。
按来源分三层:普通外链 noopener noreferrer;赞助/联盟链接 sponsored(“利益关系公开声明”信号);UGC 评论链接 ugc(表达来源是用户生成)。判类靠清单:赞助链接维护白名单、评论容器自动识别 UGC、其余按普通外链。两个工程要点:
- 站内站外用 apex 域名(主域名,忽略子域)判定,辅以
internalDomains白名单覆盖复杂后缀——按完整域名判断会误伤blog.xxx.com与www.xxx.com同一站的情况。 - 处理时机分层:静态内容构建期由转换管线补 rel(产物零运行时成本)——正文渲染的统一与安全默认值,我在《建站的技术决策》里写过完整过程,这里只讲它在 SEO 里的意义;动态 UGC 客户端观察器兜底、只在评论子树变化才重新处理;页脚、版权这类站点身份链接不参与,权重保留。
社交分享协议补全
Open Graph(社交媒体分享卡片协议)和 Twitter Card:布局统一输出标题、描述、图片、URL、站点名;分享图统一一个出口——本地图按站点格式构建压缩、远程图原样引用、未传回退默认横幅图。经常被忽略:OG 的页面类型声明、RSS 的 alternate(内容还有其他格式,比如订阅源)声明——它们应跟功能开关走,功能关了声明也别留着。
可选件:llms.txt
llms.txt 是给 AI 读的站点索引,构建期生成,与站点描述共用同一份文案源头,开关控制。规则:数据源和站点其他出口共用一份,不单独维护第二份事实。不面向 AI 检索的站跳过即可。
七步自检
上面七条是验收标准,这七步是按同一顺序能快速跑完的检查动作——每次改完照着过一遍:
- 打开一个新页面,看浏览器标签栏标题——带站点名,描述没空;
- canonical / JSON-LD / sitemap 三处 URL 尾斜杠一致;
- 查看源代码搜
<h1——这一个页面只有一个,顺着数 h2、h3 不跳级; - 字段类信息用 dl 包、图片说明用 figure,没有拿
b当强调; - 关掉一个板块开关——导航元素、sitemap、RSS 同步收敛;
- 浏览器右键查看源代码,搜
application/ld+json,把标签里的内容直接JSON.parse()——无&、<残留; - 禁用 JS 打开页面——正文、图片、链接都在。
最后
这套基建搭完后,日常新增页面基本不用碰 SEO 代码:传个 title,其他自动补齐。省的不是一两个标签,是让“每个页面都规范”不再依赖每次手动记得。七条验收标准在正文里各占一节,另有外链、社交分享与 llms 三个扩展项。重做主页时它们先立在经验里、再一条条被实现拉平——这篇就是把它成文。重做主页的经验还剩最后一块——和 AI 协作开发的纪律,将是这个系列的收尾篇:《重做个人主页:把规则写给 AI 看》。




评论
Comments评论加载中…