MOEWAH/ ARCHIVE
第26-003期

重做个人主页:静态站的 SEO 基建

MoeWah2026.08.22
重做个人主页:静态站的 SEO 基建

重做新主页时,SEO 工程层没单独排期,但验收标准先从经验里定了七条——页面标题、URL 写法、标题层级、语义化标签、随开关收敛的导航元素、JSON-LD 转义、禁用 JS 可读。这篇讲静态站 SEO 基建怎么搭稳,附带外链、社交分享与 llms 三个扩展项。

如果一个页面已经能正常打开,SEO 算做好了吗?

这次重做主页时,我依旧不会给 SEO 单独排一个阶段。URL、结构化数据、可抓取性这些东西,从一开始就跟着架构一起定了——因为它们不是上线前再补几项标签,而是贯穿整个站点的横切约束

这篇把技术篇《重做个人主页:建站的技术决策》里一笔带过的 SEO 单独拆出来,按七条验收标准一条条过:标题描述、URL 写法、标题层级、语义化标签、随开关收敛的导航元素、JSON-LD 转义、禁用 JS 可读;最后补外链、社交分享与 llms 三个扩展项。

验收标准是先从经验里定的,一共七条:

  1. 页面标题描述不散落;
  2. URL 写法一致;
  3. 一个页面只有一个 H1,层级整页连续;
  4. 标签按含义出场(键值对、图片说明);
  5. 关闭板块,导航元素残留;
  6. 结构化数据不乱转义;
  7. 禁用 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

语义化的收益长期、微小,不必完美主义,但三种情况值得做对——它们影响机器对页面信息的理解:键值对、无意义强调、图片说明

  1. 键值对用 dl/dt/dd(定义列表:一个名字配一个值)。“类型: 个人主页 / 站长: MoeWah”是字段与值的关系,用定义列表包起来,机器才分得清哪是名字哪是值;写成普通文本能看,但字段关系丢了。
<dl>
  <dt>类型</dt><dd>个人主页</dd>
  <dt>站长</dt><dd>MoeWah</dd>
</dl>
  1. 视觉强调不用 <b><b> 是无含义的样式标签;品牌名中间拆出粗体这种诉求,用 span 加 CSS 字重,语义干净。

  2. 图片加说明用 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 反转义,不再替格式有问题的内容做修正。后果:属性值被解析成字面量 &amp;、结构化数据与页面实际内容不一致、富摘要(搜索结果里的卡片展示)无法触发。这不是算法更新,是解析器对格式合规的要求变严格了。

处理原则:先生成标准 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(&lt;)。普通 & 是合法 JSON 字符不一定要转;内容经过 HTML 模板链时统一 \u0026 更稳。

重做主页时外部内容进结构化数据的处理链:渲染后 HTML 先还原纯文本 → 一次 JSON.stringify<\u003c → 构建产物逐条核对与页面可见正文一致。验证方式:看页面源代码(不是开发者工具处理后的 DOM),提取 script[type="application/ld+json"] 直接 JSON.parse(),确认没有 &amp;&lt; 残留。

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.comwww.xxx.com 同一站的情况。
  • 处理时机分层:静态内容构建期由转换管线补 rel(产物零运行时成本)——正文渲染的统一与安全默认值,我在《建站的技术决策》里写过完整过程,这里只讲它在 SEO 里的意义;动态 UGC 客户端观察器兜底、只在评论子树变化才重新处理;页脚、版权这类站点身份链接不参与,权重保留。

社交分享协议补全

Open Graph(社交媒体分享卡片协议)和 Twitter Card:布局统一输出标题、描述、图片、URL、站点名;分享图统一一个出口——本地图按站点格式构建压缩、远程图原样引用、未传回退默认横幅图。经常被忽略:OG 的页面类型声明、RSS 的 alternate(内容还有其他格式,比如订阅源)声明——它们应跟功能开关走,功能关了声明也别留着。

可选件:llms.txt

llms.txt 是给 AI 读的站点索引,构建期生成,与站点描述共用同一份文案源头,开关控制。规则:数据源和站点其他出口共用一份,不单独维护第二份事实。不面向 AI 检索的站跳过即可。

七步自检

上面七条是验收标准,这七步是按同一顺序能快速跑完的检查动作——每次改完照着过一遍:

  1. 打开一个新页面,看浏览器标签栏标题——带站点名,描述没空;
  2. canonical / JSON-LD / sitemap 三处 URL 尾斜杠一致;
  3. 查看源代码搜 <h1——这一个页面只有一个,顺着数 h2、h3 不跳级;
  4. 字段类信息用 dl 包、图片说明用 figure,没有拿 b 当强调;
  5. 关掉一个板块开关——导航元素、sitemap、RSS 同步收敛;
  6. 浏览器右键查看源代码,搜 application/ld+json,把标签里的内容直接 JSON.parse()——无 &amp;&lt; 残留;
  7. 禁用 JS 打开页面——正文、图片、链接都在。

最后

这套基建搭完后,日常新增页面基本不用碰 SEO 代码:传个 title,其他自动补齐。省的不是一两个标签,是让“每个页面都规范”不再依赖每次手动记得。七条验收标准在正文里各占一节,另有外链、社交分享与 llms 三个扩展项。重做主页时它们先立在经验里、再一条条被实现拉平——这篇就是把它成文。重做主页的经验还剩最后一块——和 AI 协作开发的纪律,将是这个系列的收尾篇:《重做个人主页:把规则写给 AI 看》

# Astro# SEO# 结构化数据# JSON-LD# llms.txt

RSS / 周刊订阅

在阅读器中接收新期刊

打开订阅
CORR

评论

评论加载中…

POWERED BY ARTALK
NOTICE / 声明本人不会主动邀请或联系任何人,任何冒用本人名义的一切事物,请务必谨防受骗!
END OF FILE · 卷终