標籤
白色卡片上的黑色 Markdown 標誌,圓角矩形內含 M 與向下箭頭

轉載換到更多讀者,同時讓自己的版本保持為主 — rel=canonical 與全文 RSS

反向連結與 rel=canonical 到底在做什麼、為什麼 Zenn 和 Qiita 會改變做法,以及讓轉載一篇文章從工程變成貼上的那條流程。

本頁目錄

引言

搜尋引擎會要求的事情,我都做過了。網站送交給 Google 與 Bing,有網站地圖、有結構化資料,robots.txt 逐一列名放行每一隻 AI 爬蟲,每一頁也都是乾淨的 HTML。唯一缺的,正好是這些通通做不出來的那一項:別人的網站連過來。

最直覺的答案是把同一篇文章也放到讀者更多的地方,而那裡有一個我當時理解得不夠、因此躲不掉的陷阱。同一篇文章的兩份複本會互相競爭,而通常是比較大的那個網站贏,用的還是你寫的字。 防止這件事的標籤是 rel=canonical,而去查哪些平台支援它,翻出了決定後續一切的事實:Zenn 和 Qiita 根本沒辦法設定。

本文整理反向連結與 canonical 實際上在做什麼、平台端的限制會逼出什麼做法,以及這個部落格早就有、把轉載從一份工程變成一次貼上的那兩樣東西。

反向連結是什麼,以及為什麼它做不出來

反向連結就是別人的網站連到你的網站。定義只有這樣,它的份量取決於連過來的是誰。

搜尋引擎把一條連結當成一張小小的票。一個一票都沒有的新網域,與其說被懲罰,不如說是沒人認識,而這個沒人認識,正是限制爬蟲多常回來的東西。其他所有 SEO 工作,都是讓爬蟲來了之後能讀懂這一頁。反向連結則是讓它來的那一半。

所以在其他事情都做完之後,只剩下這一項。網站地圖是我可以自己產生的檔案。結構化資料是我可以自己輸出的標記。別的網站連過來,是另一個人做的決定,而唯一誠實的影響方式,是把文章放到那些可能會連過來的人本來就在的地方。

連結從哪裡來,哪一種會產生複本

反向連結就是一條連結。做出一條連結不需要複製任何東西。有人寫下「看看這個」並指向你的網址,機制就是這樣。這件事值得明講,因為下一節談的是轉載,而這兩件事很容易被讀成同一個動作。

它們不是。轉載是把文章的複本放到一個本來就有讀者的平台上,而製造出 rel=canonical 要解決的那個問題的,正是那份複本。被分享出去的連結不會產生複本,所以沒有東西要競爭,也沒有 canonical 要設定。

你做的事 會複製文章 需要 canonical 目的是什麼
分享連結 不會 不需要 觸及,以及被發現
在派得上用場的地方留言附連結 不會 不需要 觸及,而且在已被信任的網域上
轉載全文 需要 那個平台的讀者
別人連到你 不會 不需要 真正影響排名的信號

社群平台上的連結幾乎全是 nofollow Facebook、X 與 LinkedIn 預設就會替外部連結加上它。Google 在 2019 年 9 月把 nofollow 從指令改成提示,隔年 3 月又把這個處理方式延伸到檢索與索引,所以這種連結並非毫無價值。它也不是可以拿來規劃的東西。

分享真正穩定做到的,是把文章放到某個擁有自己網站的人面前。 會被跟隨的那條連結是他寫的。要走到上表最後一列,只有這一條路,而本文描述的任何機制都無法自動化它。

rel=canonical 在做什麼

rel=canonical 是頁面 <head> 裡的一行:

<link rel="canonical" href="https://oharu121.com/blog/some-article/">

它的意思是:你正在讀的這一頁是複本,原文在那個網址。 願意採信它的搜尋引擎會把兩者收攏成一個,並把原文當作要顯示的那一個。

沒有它的話,兩個內容相同的頁面看起來就像兩個剛好一致、彼此無關的頁面。引擎會挑一個,而它挑的依據是信任,那是大平台比個人部落格多的東西。

一篇文章、兩份複本、一個標籤用同樣的兩份複本比較兩種情況。沒有 canonical 標籤時,搜尋引擎會把它們當成兩個內容相同但彼此無關的頁面,並挑選它比較信任的那一個,也就是比較大的網站。在轉載的那一份加上指向原文的 canonical 標籤後,兩者會被視為同一篇文章,出現在搜尋結果裡的是原文,而讀者仍然會從轉載那一份過來。沒有 canonical 標籤你的部落格原文比較大的網站轉載搜尋引擎比較大的網站轉載兩個內容相同卻無關的頁面,它會挑比較信任的那個canonical 指向原文你的部落格原文比較大的網站轉載rel=canonical →搜尋引擎你的部落格原文一篇文章、兩個網址,顯示出來的是原文
兩份複本本身完全沒變。決定哪一份被當成文章的,是一個標籤。

有兩件事值得寫精確,因為兩者都容易被誇大。

它是提示,不是指令。 Google 的文件明確寫著 canonical 只是眾多信號之一,夠強的相反信號可以蓋過它。實務上它會被採信,而且這是你唯一能握住的槓桿。

它不會把讀者送去任何地方。 對正在讀複本的人來說,canonical 是看不見的。他們會留在那個平台上,而這沒問題:你從轉載想要的是那條連回來的連結,不是瀏覽數。

Zenn 與 Qiita 送不出這個標籤

代理程式最初的建議,是把文章轉載到 dev.to、Zenn 與 Qiita,並讓 canonical 指回原文,同時註明未經查證。查證之後,做法改了。

dev.to 可以逐篇設定 canonical 網址,欄位叫 canonical_url,而且從 RSS 訂閱源匯入時還能自動設好。

Zenn 不行。 一篇正是在調查這件事的文章指出,不論原始檔案的 frontmatter 怎麼寫,Zenn 公開頁面上的 canonical 都指向 Zenn 自己的網址。寫了也不會改變爬蟲看到的任何東西。

Qiita 同樣沒有讓使用者設定的 canonical。

是否支援 canonical,決定你該貼什麼三個平台各自允許什麼的對照表。dev.to 可以逐篇設定 canonical 網址,也支援從訂閱源匯入,所以在那裡轉載全文是安全的。Zenn 不行:不論原始檔案怎麼寫,它公開頁面的 canonical 都指向 Zenn 自己。Qiita 同樣沒有讓使用者設定 canonical 的地方。在這兩個平台上,安全的做法是放一段摘要並連回原文,因為全文會和原文互相競爭。能否宣告這是你的複本該貼的內容dev.to可以,逐篇設定全文Zenn不行,永遠指向自己摘要與連結Qiita沒有這個設定摘要與連結重點不在平台大小,而在於平台願不願意說這篇文章是你的。
這不是平台大小的問題,而是平台願不願意說這篇文章是你的。

如果那個建議沒查證就照做,結果會和目標完全相反:把每一篇文章的全文交給兩個權重高得多的網站,而任何地方都沒有東西說明哪一份複本才是先來的。

這個限制逼出來的是分流,不是撤退。canonical 可用的地方就轉載全文;不能用的地方就放一段簡短摘要並連回原文。摘要一樣觸及得到那些讀者,一樣換得到連結,只是不去參加一場注定會輸的競爭。

這個部落格為了別的目的早就做好的東西

先前的兩項變更在這裡派上了用場,而它們都不是為了這件事做的。

每篇文章的 Markdown 複本。 每篇文章都提供兩次:一次是頁面,一次是同一網址結尾加上 .md 的純 Markdown。當初做這個是為了讓回答引擎拿到內文,而不必先剝掉一層導覽外殼。結果它也正好就是你要貼進另一個編輯器的東西。

RSS 訂閱源改成全文。 每一則項目以前只有 160 個字的摘要和一條連結,現在帶著整篇文章,因為平台的匯入功能讀到只有摘要的訂閱源,只能生出一份空殼草稿。

一個原始檔、三種輸出、三個去處每篇文章的單一 MDX 檔案會產生三樣東西。算繪後的頁面是讀者與搜尋引擎拿到的東西。同一個網址加上 .md 的 Markdown 複本是純文字,可以直接貼進別的編輯器。訂閱源現在會以全文帶著同一份內容,那正是平台的匯入功能能抓取的。頁面服務網站本身,複本服務手動轉載,訂閱源服務自動轉載。一個 .mdx 檔案頁面讀者與爬蟲網站本身.md 複本可以貼到任何地方手動轉載訂閱源全文、20 筆匯入功能這三樣都不是為了轉載而做的,結果三樣都派上了用場。
一個原始檔早就把三樣都生出來了,為了這件事另外加的只有最後一樣。

訂閱源這一邊有個 Markdown 複本沒有的限制。compiledContent() 不支援 MDX,而這裡每一篇文章都是 MDX,所以慣用的那條路並不存在。代理程式的做法是直接重用產生 Markdown 複本的那段轉換,再用 Astro 自己的 markdown 處理器把輸出算繪出來:

processor ??= createMarkdownProcessor({});
const { code } = await (await processor).render(mdxToMarkdown(body));

訂閱源承接了 Markdown 複本失去的東西。 圖表以圖說的形式抵達,照片以替代文字的形式抵達,因為兩者都沒有離開頁面之後還成立的網址。內文、標題、表格與程式碼都原樣通過。比起原本的兩句話,這是很大的進步,但它不等於那個頁面。

訂閱源的兩個錯誤,以及它們的共通點

這個訂閱源上線時帶著兩個代理程式寫出來的缺陷,而兩個都是靠審查找到的,不是靠任何檢查。

根相對連結漏了出去。 有一篇文章以 /blog/… 連到自己的第一部分。瀏覽器會用它所在的頁面來解析這個路徑。訂閱源閱讀器沒有那個頁面,於是用它自己的來源去解析,結果 404。三種語言,每個訂閱源各一條。

驗證當時回報一切正常。它找的是 ./,而實際的連結是 /,所以那個形狀根本不在檢查的視野裡。

只要混進一個控制字元,訂閱源就會無法解析。 Markdown 的轉換在改寫標籤之前,會用 NUL 哨兵把程式碼區段遮起來,最後再還原。在 .md 檔案裡,殘留的哨兵只是外觀問題。在 XML 裡則是致命的,而建置過程並不會驗證訂閱源:

a NUL inside an element parses: FAIL — not well-formed (invalid token)

pnpm checkpnpm build 都會維持綠燈,而每一個閱讀器都無法解析這個訂閱源。

兩者現在都處理掉了:href="/…"src="/…" 會以網站來源解析,XML 禁止的控制字元範圍也會被移除。這兩者的共通點是,本來該抓到它們的那個東西,是同一個人、在同一時間、帶著同一個盲點寫出來的。 檢查和被檢查的對象彼此一致,不算證據。

操作步驟

針對一篇已經可以轉載的文章。

在支援 canonical 的平台上,轉載全文。要貼的東西 Markdown 複本已經備好了:

終端機視窗
curl -sS https://oharu121.com/blog/<slug>.md | pbcopy

把 Markdown 複本開頭的標頭區塊連同那條 --- 分隔線一起刪掉,否則它會和平台自己的 frontmatter 分隔符號打架。接著設定 canonical:

---
title: <the title>
published: true
canonical_url: https://oharu121.com/blog/<slug>/
tags: seo, astro, webdev
---

確認它生效了。 打開發布後那一頁的原始碼,找一個指向你網址的 <link rel="canonical">。如果沒有,就是 frontmatter 沒有被解析,原因通常是那條忘了刪的 ---

在不支援的平台上,寫兩三段並連回原文。這個部落格已經有日文與繁體中文的翻譯,所以摘要可以用讀者的語言寫,並指向該語系的網址。

在連結比轉載更有價值的場合,到有人正好卡在這篇文章所解決的問題的那個 issue 底下留言。留言本身必須站得住腳,連結是完整版本,不是主體。

可以期待什麼

誠實的答案並不令人滿意,所以直說。

反向連結不是開關。 這些都不會讓一篇文章明天就排上去。改變的是爬蟲有沒有理由再回來,而那是慢慢累積的。

先到的是推薦流量,對搜尋的影響要晚得多,如果會來的話。平台上的讀者是立即的。再往下就取決於他們之中有沒有人寫了什麼並連過來,而那不是一條流程造得出來的事。

這套工具真正換到的,是嘗試的成本。 轉載以前是每篇文章、每個平台都要複製、貼上、重排一次的雜事,而那正是會安靜地不再發生的那種雜事。現在是一行 curl 加一段 frontmatter。這條流程不會讓轉載變得更有效,它只是讓轉載便宜到可以一直做下去。

總結

  • 反向連結是別人網站連過來的連結,也是這整件事裡唯一沒有人能自己生出來的部分。 其他每一項,都只是讓爬蟲來了之後能讀懂這一頁。
  • 分享連結和轉載文章是兩個不同的動作。 只有轉載會產生複本,也只有複本需要 canonical。社群連結幾乎全是 nofollow,而自 2019 年起那是提示而非指令:值得為了觸及去做,不值得當成排名信號來計算。
  • rel=canonical 宣告哪一份複本是原文,讓轉載不會變成和自己競爭。它是提示而不是指令,也不會帶動任何讀者。
  • 最容易想到的三個平台裡有兩個設不了它。 不論原始檔怎麼寫,Zenn 的頁面都把 canonical 指向 Zenn,而 Qiita 沒有這個設定。全文去 canonical 可用的地方,摘要與連結去其他所有地方。
  • Markdown 複本與全文訂閱源是為了回答引擎而做的,結果變成了轉載的流程。一個是拿來貼的,另一個是匯入功能會讀的。
  • 和程式碼一起寫出來的檢查,會共用它的盲點。 訂閱源的驗證找的是 ./,而壞掉的連結是 /
  • 該期待的是嘗試成本下降,而不是結果到來。 這仍然是有用的改變,因為每個平台要花掉半天的那個版本,就是不會被做的版本。

參考連結

分享這篇文章