
転載で読者を増やしつつ自分の記事を主にしておく — rel=canonicalと全文RSS
被リンクとrel=canonicalが何をするのか、ZennとQiitaで方針が変わる理由、そして記事の転載を作業ではなく貼り付けに変えたパイプラインの話。
目次
はじめに
検索エンジンが求めることは一通りやってありました。サイトはGoogleとBingに登録し、サイトマップも構造化データもあり、robots.txtはAIクローラーを1つずつ名指しで許可し、どのページも素直なHTMLです。足りていなかったのは、そのどれもが生み出さない唯一のもの、他のサイトから自分のサイトへのリンクでした。
すぐ思いつく答えは、同じ記事を読者の多い場所にも出すことです。そしてそこには、避けられるほど理解していなかった落とし穴がありました。1つの記事の2つの複製は競合し、たいていは大きいサイトのほうが、こちらの書いた文章で勝ちます。 それを防ぐタグがrel=canonicalで、どのプラットフォームが対応しているかを調べたところ、その後のすべてを決める事実が出てきました。ZennとQiitaはそもそも設定できません。
本記事では、被リンクとcanonicalが実際に何をするのか、プラットフォーム側の制約が何を強いるのか、そして転載を作業から貼り付けに変えた、このブログに既にあった2つのものを整理します。
被リンクとは何か、そしてなぜ自力で作れないのか
被リンクとは、他人のサイトから自分のサイトへのリンクです。定義はそれだけで、重みはリンクする側が誰かによって決まります。
検索エンジンはリンクを小さな票として扱います。票が1つもない新しいドメインは、罰されているというより知られていない状態で、その知られていなさが、クローラーがどれだけ戻ってくるかを決めています。他のSEO施策はすべて、クローラーが来たあとにページを読めるようにするものです。被リンクは、来させるほうの一部です。
だからこそ、他がすべて終わったあとにこれだけが残りました。サイトマップは自分で生成できるファイルです。構造化データは自分で出力できるマークアップです。他サイトからのリンクは誰か別の人が下す判断であり、それに影響を与える誠実な方法は、リンクしてくれそうな人がすでにいる場所に文章を置くことだけです。
リンクはどこから来るのか、そしてどれが複製を作るのか
被リンクはリンクです。作るために何かを複製する必要はありません。誰かが「これを見て」と書いてこちらのアドレスを指す、それが仕組みです。あえてはっきり書いておくのは、次の節が転載の話で、その2つは同じ動きに読めてしまうからです。
同じではありません。転載は、すでに読者がいるプラットフォームに記事の複製を置く行為で、rel=canonicalが解決すべき問題を作るのはその複製のほうです。共有されたリンクは複製を作らないので、競合する相手もなく、設定すべきcanonicalもありません。
| すること | 記事を複製する | canonicalが要る | 何のためか |
|---|---|---|---|
| リンクを共有する | しない | 要らない | 到達と、見つけてもらうこと |
| 役に立つ場所にリンク付きでコメントする | しない | 要らない | 到達、しかも信頼済みのドメインで |
| 全文を転載する | する | 要る | そのプラットフォームの読者 |
| 誰かがこちらにリンクする | しない | 要らない | 順位に効く信号そのもの |
SNSからのリンクはほぼすべてnofollowです。 Facebook、X、LinkedInは既定で外部リンクにそれを付けます。Googleは2019年9月にnofollowを指示からヒントへ変え、翌年3月にはクロールとインデックスにもその扱いを広げたので、この種のリンクに価値がないわけではありません。ただ、当てにして計画を立てるものでもありません。
共有が確実にやってくれるのは、自分のサイトを持っている人の前に文章を置くことです。 nofollowの付かないリンクを張るのはその人のほうです。上の表の最終行にたどり着く道はそれだけで、ここに書いたどの仕組みも自動化できません。
rel=canonicalは何をするのか
rel=canonicalはページの<head>にある1行です。
<link rel="canonical" href="https://oharu121.com/blog/some-article/">意味は、いま読んでいるこのページは複製であり、原本はそのアドレスにある、です。これを尊重する検索エンジンは2つを1つにまとめ、表示するものとして原本を扱います。
これがないと、同じ本文を持つ2ページは、たまたま一致した無関係な2ページに見えます。エンジンはどちらかを選び、その基準は信頼で、大きなプラットフォームは個人ブログよりそれを多く持っています。
正確に書いておくべきことが2つあります。どちらも過大に語られやすいからです。
これは指示ではなくヒントです。 Googleのドキュメントは、canonicalが複数ある信号の1つであり、十分に強い反対の信号があれば上書きされうると明記しています。実際には尊重されますし、こちらが持てるレバーはこれだけです。
読者をどこかへ送るものではありません。 canonicalは複製を読んでいる人には見えません。その人はプラットフォームに留まりますが、それでかまいません。転載から欲しかったのは戻ってくるリンクであって、ページビューではないからです。
ZennとQiitaはそれを送れない
エージェントの最初の提案は、canonicalを原本に向けたうえでdev.to・Zenn・Qiitaへ転載する、というもので、未確認と但し書きが付いていました。確認した結果、方針が変わりました。
dev.toは記事ごとにcanonical URLを設定できます。 canonical_urlというフィールドで、RSSフィードからの取り込み時に自動で設定することもできます。
Zennはできません。 まさにこれを調べた記事によると、Zennの公開ページはソースのフロントマターに何を書いてもZenn自身のURLを指すcanonicalを持ちます。書いてもクローラーから見えるものは何も変わりません。
Qiitaにもユーザーが設定できるcanonicalはありません。
あの提案を確認せずに実行していたら、結果は目的の正反対でした。全記事の全文を、はるかに権威のある2つのサイトに渡し、どちらの複製が先だったかを示すものはどこにもない状態です。
この制約が強いるのは撤退ではなく分岐です。canonicalが使えるところでは全文を転載する。使えないところでは短い要約を出して原本にリンクする。要約でもその読者には届き、リンクも得られます。負けると分かっている競争に加わらないだけです。
別の目的でこのブログに既にあったもの
先に入れた2つの変更が、ここで効きました。どちらもこのために作ったものではありません。
全記事のMarkdown版。 各記事は2通りで配信されています。ページとして1回、同じアドレスの末尾に.mdを付けたプレーンなMarkdownとして1回です。これは回答エンジンがナビゲーションの外殻なしに本文を得られるように作りました。そしてそれは、別のエディタに貼り付けるものそのものでもあります。
RSSフィードの全文化。 各項目はかつて160文字の要約とリンクだけでした。いまは記事を全文で運びます。要約だけのフィードを読んだプラットフォームの取り込み機能は、中身のない下書きしか作れないからです。
フィード側にはMarkdown版になかった制約がありました。compiledContent()はMDXでは使えず、ここの記事はすべてMDXなので、通常の経路が存在しません。エージェントの方法は、Markdown版を作っている変換をそのまま再利用し、その出力をAstro自身のmarkdownプロセッサで描画するものです。
processor ??= createMarkdownProcessor({});const { code } = await (await processor).render(mdxToMarkdown(body));フィードはMarkdown版が失うものをそのまま受け継ぎます。 図版はキャプションとして、写真は代替テキストとして届きます。どちらもページを離れても成立するアドレスを持たないからです。本文、見出し、表、コードはそのまま通ります。2文だった頃から見れば大きな前進で、ページと同じものではありません。
フィードの2つのバグと、その共通点
このフィードにはエージェントが書いた欠陥が2つあり、どちらもチェックではなくレビューで見つかりました。
ルート相対のリンクが漏れていました。 ある記事が自分のパート1へ/blog/…としてリンクしています。ブラウザはそれを、いま開いているページを基準に解決します。フィードリーダーにはそのページがないので、自分のオリジンを基準に解決して404になります。3言語すべてで、フィードごとに1件ずつです。
検証は問題なしと報告していました。探していたのは./で、実際のリンクは/だったので、その形はチェックの視野に入っていませんでした。
制御文字が1つ紛れ込めば、フィードは解析できなくなります。 Markdownの変換はタグを書き換える前にコードスパンをNULの番兵で覆い、最後に戻します。.mdファイルなら番兵の残りは見た目の問題です。XMLでは致命的で、しかもビルドはフィードを検証しません。
a NUL inside an element parses: FAIL — not well-formed (invalid token)pnpm checkもpnpm buildも緑のまま、すべてのリーダーがフィードの解析に失敗する状態になります。
どちらも対処済みです。href="/…"とsrc="/…"はサイトのオリジンを基準に解決し、XMLが禁じる制御文字の範囲は取り除きます。2つに共通するのは、これを捕まえるはずだったものが、同じ人間によって、同じ時に、同じ盲点を持って書かれていたことです。 チェックと検査対象が一致していることは、証拠ではありません。
手順
転載できる状態の記事について。
canonicalに対応しているプラットフォームでは、全文を転載します。貼り付けるものはMarkdown版がそのまま使えます。
curl -sS https://oharu121.com/blog/<slug>.md | pbcopyMarkdown版の先頭のヘッダー部分は---の区切りごと削除します。残しておくと、プラットフォーム側のフロントマターの区切りと衝突します。そのうえでcanonicalを設定します。
---title: <the title>published: truecanonical_url: https://oharu121.com/blog/<slug>/tags: seo, astro, webdev---効いているか確認します。 公開後のページのソースを表示し、自分のアドレスを指す<link rel="canonical">があるかを見ます。ないならフロントマターが解釈されておらず、原因はたいてい消し忘れた---です。
対応していないプラットフォームでは、2〜3段落を書いて原本にリンクします。このブログには日本語と繁体中国語の翻訳が既にあるので、要約は読者の言語で書き、そのロケールのURLを指せます。
リンクのほうが転載より価値がある場面では、その記事が解決する問題に誰かがぶつかっているissueにコメントします。コメントはそれ単体で成立している必要があり、リンクは長い版であって本体ではありません。
何を期待できるか
正直な答えは物足りないので、はっきり書いておきます。
被リンクはスイッチではありません。 これで記事が明日から上位に出るようなことはありません。変わるのは、クローラーが戻ってくる理由があるかどうかで、それはゆっくり積み上がります。
先に来るのは参照トラフィックで、検索への効果はずっと後です。 来るとすれば、ですが。プラットフォームの読者はすぐに来ます。その先は、そのうちの誰かが何かを書いてリンクするかどうか次第で、それはパイプラインが起こせることではありません。
この仕組みが実際に下げたのは、試すコストです。 転載はこれまで、記事ごと・プラットフォームごとにコピーと貼り付けと整形をする作業でした。そういう作業は静かにやらなくなります。いまはcurlが1つとフロントマターが1ブロックです。このパイプラインは転載をうまくするものではありません。続けられる程度に安くするものです。
まとめ
- 被リンクは他人のサイトからのリンクであり、ここで唯一、誰にも生成できない部分です。 他はすべて、クローラーが来たあとにページを読めるようにするものです。
- リンクの共有と記事の転載は別の動きです。 複製を作るのは転載だけで、canonicalが要るのは複製だけです。SNSのリンクはほぼすべて
nofollowで、2019年以降それは指示ではなくヒントです。到達のためにやる価値はあり、順位の信号として数える価値はありません。 rel=canonicalはどちらの複製が原本かを宣言します。 これで転載が自分自身との競争にならずに済みます。指示ではなくヒントで、読者を動かすものでもありません。- すぐ思いつく3つのうち2つはそれを設定できません。 Zennの公開ページはソースに何を書いてもZennを指し、Qiitaには設定項目がありません。全文はcanonicalが使えるところへ、要約とリンクはそれ以外のすべてへ。
- Markdown版と全文フィードは回答エンジンのために作ったもので、結果として転載のパイプラインになりました。片方は貼り付けるもの、もう片方は取り込み機能が読むものです。
- コードと並べて書かれたチェックは、そのコードの盲点を共有します。 フィードの検証は
./を探し、壊れていたリンクは/でした。 - 期待すべきは結果の到着ではなく、試すコストが下がることです。 それでも有用な変化です。プラットフォームごとに半日かかる版は、やらなくなる版だからです。