谁在博客提到了你:给Hugo静态博客添加Webmention

Webmention是W3C制定的标准, 可以让你知道哪些博客提到了你的文章。

有关如何配置,我觉得Eltrac写的简体中文的Webmention简明指南已经介绍得非常清楚。珠玉在前,本文基本是在记录我如何用尽量简单的方式 (在博客上糊弄出了这个功能) 在Hugo静态博客里接收和显示这些提及。 顺便昭告天下:本站支持Webmention了。

目前本站的Webmention区样式如下:

一个“外站提及”列表。当其他独立博客的文章里链接并讨论了你的文章时,这里会清爽地列出对方的名字、文章标题、发布日期以及来源网站,让互联网站点之间能够互相看到彼此的讨论。

如果你已经知道Webmention是什么、如何收发,可以直接跳到第5部分「在博客上显示Webmention」。

1 什么是Webmention #

假设Alice发布了一篇文章,网址是https://alice.com/aaa。

Bob读到之后觉得很不错,在自己的网站上写了一篇文章提到它,并附上了Alice文章的网址。 Bob的文章的网址是https://bob.com/bbb。

Bob可以向Alice的网站发送一条消息,告诉她「我在https://bob.com/bbb提到了你的https://alice.com/aaa」,这样Alice就知道她写的内容被人提及了。 这条消息当然可以用邮件或聊天软件发送。 但如果Alice的网站能接收Webmention消息,Bob也可以用Webmention发送。1

发送Webmention不需要写代码,用现成的线上服务就能做到。 例如,打开webmention.app,在页面下方的「Want to just try it out?」栏位输入Bob的文章网址。它会找出文章中的外部链接。点发送,Alice就能收到消息了。(它甚至不强求Bob的网站支持Webmention。具体请见本文第3部分。)

本站现在已经支持webmention。如果你在自己的网站上提到了这篇文章,并且附上了链接,现在可以跳到文章末尾的「送出Webmention」向我发送webmention消息。本站在下次更新时,会将你的文章链接显示在文末。

2 为什么想要Webmention #

我其实把Webmention当成博客卷(blogroll)的另一种形式。

查论文时,有些网站会提供cited by(“被以下文章所引”),非常有用。可以快速地找到有用的文章以及臭味相投的研究者。 Webmention就好像独立博客的cited by功能。

传统友情链接页面很难快速知道这些网站写什么、风格如何。 (所以有些博客会在友链页展示朋友的最新文章,也有人会给每个订阅源写一小段介绍。) 我会在其他人的博客留言区和Webmention区找好玩的博客。既然我们对同一篇文章感兴趣,那我也许会对你的网站感兴趣……?

3 如何发送Webmention #

Webmention只是一个技术标准,不是某个具体的软件。 就像不同的邮箱软件可以互相收发邮件一样,符合标准的网站和工具都可以互相发送Webmention消息。 只要对方网站有Webmention接收端点,且你的文章中正确地包含了对方文章的链接。2

最简单的方式之一是使用前面提到的webmention.app。输入自己文章的网址,确认它识别出的站外链接正确无误,点击发送即可(如下图)。这里的「Source」是自己的文章网址,「Target」是对方文章的网址。

webmention.app网页界面,从上到下依次是「Source」源文章网址、「Target」目标网址以及发送按钮
webmention.app网站截图

如果希望自己的作者名称、头像和文章内容能更准确地显示在对方网站上,可以为网页加入microformats标记。 常见的标记包括h-entry(文章)和h-card(作者资料卡)。 如果能读英文的话,Aaron Parecki的这篇指南《从零开始发送你的第一条Webmention》非常简明易懂。 Spike《给博客添加 Webmention》的「作为发送方」部分是很好的中文参考。

我的microformats标记大致如下。 我不想在文章页面上显示作者资料卡,所以给它加上了hidden。

<article class="h-entry">
  <h1 class="p-name">示例文章标题</h1>
  <div class="e-content">
    <p>这是文章正文。</p>
  </div>
  <div class="p-author h-card" hidden>
    <a class="p-name u-url" href="https://alice.com/">
      Alice
    </a>
    <img class="u-photo" src="https://alice.com/avatar.jpg" alt="">
  </div>
</article>

4 如何接收Webmention #

就像发邮件时对方起码得有一个邮箱。要接收Webmention,网站需要有一个接收端点。 最简单常用的是第三方服务webmention.io,它免费且开源,由Aaron Parecki提供。 注册后可以直接在它的仪表盘查看收到的消息,也可以通过RSS订阅新消息提醒。

首先需要在博客的<head>加入:

<link rel="webmention" href="https://webmention.io/我的博客网址/webmention">
<link rel="me" href="https://github.com/我的帐号">

第一行告诉别人「消息请寄到这里」。 第二行用来向webmention.io证明这个网站是我的。 也可以用其他方式证明(例如电子邮箱),登录时依提示操作即可。

把修改后的博客部署上线,然后打开webmention.io,輸入博客网址,登录。 接下来webmention.io就能够替我接收Webmention消息了。

在博客上放一个表单 #

可以在博客上放置一个入口,让访客便捷地送出webmention。

我一开始参考了Spike的实现方式,直接放上我的Webmention Endpoint链接,让访客跳转到webmention.io提交。

现在则参考了Eltrac的「如何制作一个 Webmention 表单?」,做成了表单的形式:

一个“送出webmention”表单。文字内容为:如果你在自己的網站上提到了這篇貼文,並附上了連結,歡迎在下方輸入你文章的網址。你的網址將在本站下次更新後展示在本頁。讓我們把互聯網織成網 :) 瞭解更多:什麽是Webmention?

5 在博客上显示Webmention #

显示Webmention消息的过程分成三段:

  1. 别人发出消息:“我这篇文章链接到了你那篇。”
  2. webmention.io替我接收和储存这些消息。
  3. 网站构建时把webmention.io的消息抓下来,渲染到页面上。

我的网站使用Hugo制作,部署在Netlify。 我对实时渲染没有执念,Webmention链接不会实时出现,而是在网站每次构建时一起更新。(就这样以纯静态的形态出街!) 你如果想要实时渲染,可以直接借助前人造好的轮子webmention.js。

第一步,前往webmention.io的设定页,复制API Key。

这个API Key能读取你域名下的所有消息,所以不要贴到网页里,也不要提交到GitHub,要存放在部署平台的环境变量中。本文以Netlify为例,请到Netlify后台的Site configuration,打开Environment variables,新建变量,名称填WM_TOKEN,值粘贴刚才的API Key。勾选Contains secret values,这样它不会出现在界面和构建日志里。

第二步,在项目根目录(和hugo.toml同一层)新建netlify.toml,写:

[build]
  publish = "public"
  command = "mkdir -p data && (curl -sf -o data/webmentions.json \"https://webmention.io/api/mentions.jf2?domain=我的博客网址&token=$WM_TOKEN&per-page=1000\" || echo '{\"children\":[]}' > data/webmentions.json) && hugo --gc --minify"

这段指令会在网站构建的时候先建立data资料夹,再从webmention.io下载消息,最后照常运行Hugo。如果下载失败,就建立一份空数据,让网站仍可继续构建。

请将「我的博客网址」换成真实网址, 最后的 hugo --gc --minify 请换成你原本在Netlify后台设定的Hugo构建指令。

接着在.gitignore,加一行:

data/webmentions.json

这个文件每次构建都会重新生成,不需要放进版本库。

第三步,在博客文章模板中加上显示Webmention消息的代码。

例如
  {{- $mentions := slice -}}
  {{- if .Params.webmentionDemo -}}
    {{- $mentions = (hugo.Data.webmention_sample | default dict).children | default slice -}}
  {{- else -}}
    {{- $all := (hugo.Data.webmentions | default dict).children | default slice -}}
    {{- $here := strings.TrimSuffix "/" .Permalink -}}
    {{- range $all -}}
      {{- $target := strings.TrimSuffix "/" (index . "wm-target") -}}
      {{- $prop := index . "wm-property" | default "mention-of" -}}

      {{- if and (eq $target $here) (in (slice "mention-of" "in-reply-to") $prop) -}}
        {{- $mentions = append $mentions . -}}
      {{- end -}}
    {{- end -}}
  {{- end -}}
  {{- $verbs := dict "mention-of" "提及了这篇文章" "in-reply-to" "回复了这篇文章" -}}
  {{- with $mentions -}}
  &lt;section class="webmentions"&gt;
    &lt;h2&gt;外站提及(Webmention)&lt;/h2&gt;
    &lt;ul&gt;
      {{- range . -}}
        {{- $url := .url | default "" -}}
        {{- $name := .author.name | default "匿名作者" -}}
        {{- $title := .name | default "" -}}
        {{- $prop := index . "wm-property" | default "mention-of" -}}
        {{- $verb := index $verbs $prop | default "提及了这篇文章" -}}
        {{- with .content.html -}}
          {{- if not $title -}}
            {{- with findRESubmatch `(?is)&lt;h[1-6][^&gt;]*class="[^"]*\bp-name\b[^"]*"[^&gt;]*&gt;(.*?)&lt;/h[1-6]&gt;` . 1 -}}
              {{- $title = index (index . 0) 1 | plainify | htmlUnescape | strings.TrimSpace -}}
            {{- end -}}
          {{- end -}}
        {{- end -}}
        &lt;li&gt;
          &lt;strong&gt;{{ $name }}&lt;/strong&gt;
          {{- if $title }}
            在《&lt;a href="{{ $url | safeURL }}" rel="nofollow ugc noopener"&gt;{{ $title | htmlEscape | truncate 60 }}&lt;/a&gt;》中{{ $verb }}。
          {{- else }}
            在 &lt;a href="{{ $url | safeURL }}" rel="nofollow ugc noopener"&gt;{{ $url | htmlEscape | truncate 45 }}&lt;/a&gt; {{ $verb }}。
          {{- end }}
        &lt;/li&gt;
      {{- end -}}
    &lt;/ul&gt;
  &lt;/section&gt;
  {{- end -}}

它会读取指向当前文章的webmention消息,逐条显示作者名和链接,没有作者名时显示链接本身。 样式可以按自己的博客风格再调整。

没有人提及时,怎么测试样式? #

刚开始肯定没有任何消息,可以用假数据检查排版。

  1. 新增 data/webmention_sample.json:
{
  "children": [
    {
      "url": "https://alice.com/blog/aaa",
      "name": "旅行日记",
      "author": { "name": "Alice", "url": "https://alice.com" },
      "published": "2026-10-01T09:00:00",
      "wm-received": "2026-10-02T08:00:00Z",
      "wm-property": "mention-of"
    },
    {
      "url": "https://example.net/post/1",
      "author": { "name": "", "url": "" },
      "wm-received": "2026-10-06T08:00:00Z",
      "wm-property": "like-of"
    }
  ]
}
  1. 新增测试页 content/wm-demo.md:
---
title: "Webmention样式测试"
webmentionDemo: true
sitemap:
  disable: true
---
测试页,确认后删除。

预览时,打开 /wm-demo/ 就能看到效果。 确认没问题后,删掉这两个档案,不要合并进主分支。

注意事项 #

  • 预览的时候看不到Webmention消息是正常的,因为预览站的网址和正式网址不同,匹配不上。
  • webmention.io是第三方免费服务,建议每隔一段时间导出数据备份。

做完以上步驟,Alice的博客就有了一个不需要JavaScript、不追踪读者、每次构建才更新的Webmention评论区。

6 暂时不打算做的事 #

不打算把Fediverse等社交媒体上的互动桥接成标准的webmention、同步到博客。 之前用Bearblog建站的时候短暂尝试过把Fediverse上的公开回复(=发布时可见范围被设定为公开可见的回复)同步到博客。后来还是删掉了这个功能。我有和Terence Eden相似的顾虑。 毕竟公开留言不代表留言者同意自己的头像和文字出现在别人的博客界面上。 另外,看起来有人停用Webmention,也是因为把社交媒体互动桥接过来后,博客上的webmention通知变得多且嘈杂,反而让自己很受干扰。3

我也不打算自动发送Webmention,暂时会像处理邮件一样手动处理。 自动化当然更方便,但我对自己写Bug的水平很有信心,做成自动程序一定会不小心打扰到人。

7 有帮助的资源 #

中文:

英文:


  1. 这个例子totally洗稿Eltrac,但它真的简明易懂,我一定要放。 ↩︎

  2. ref. Do webmentions work only when both sites use webmentions?(只有双方网站都支援 Webmention 才能使用吗?), IndieWeb,2026年10月11日访问. ↩︎

  3. ref. Groeneveld, Wouter(2023年5月8日). Why I Retired My Webmention Server(我为什么停用了自己的Webmention服务器),2026年10月20日访问. ↩︎

如果你在自己的網站上提到了這篇貼文,並附上了連結,歡迎在下方輸入你文章的網址。你的網址將在本站下次更新後展示在本頁。讓我們把互聯網織成網 :)

瞭解更多:什麽是Webmention?

?隨機抽選一篇