タグ: SEO

  • リンク先は存在した。それでも404だった

    リンク先は存在した。それでも404だった

    連載の記事に「次回:〇〇」というリンクを書いています。そこを押すと404でした。

    リンク切れは事前に確認していました。結果は0件でした。確認の仕方が間違っていたのです。

    この記事で扱うこと

    存在するのに辿り着けないリンクの話です。確認が通ってしまった理由、直し方を3つ比べて1つを選んだ過程、そして表示するときに判定する実装を書きます。

    何を数えていたか

    確認の中身はこうでした。全記事の本文から /blog/ で始まるリンクを抜き出し、その行き先のスラッグがCMSに存在するかを見る。存在しないものがあれば、それがリンク切れです。

    この方法で、112本ぶんを突き合わせて0件でした。

    存在するかどうかを見ていた

    CMSの中には、公開済みの記事と、予約投稿の記事が両方入っています。

    連載の第1回を公開した時点で、第2回はまだ予約投稿の状態です。CMSには在るので、確認は通ります。しかしサイトに出ているのは公開済みのものだけなので、押すと404に着きます。

    2つの集合がずれている

    整理すると、こうなっていました。

    含まれるもの
    CMSにある記事 公開済み + 予約投稿 + 下書き
    サイトに出ている記事 公開済みだけ

    確認していたのは上の集合で、読む人が見ているのは下の集合でした。差の部分が、そのまま404になります。

    書いている側からは見えません。管理画面では第2回が普通に並んでいますし、編集もできます。書き手の視界には、公開前の記事も入っているからです。

    直し方を3つ比べた

    案 やめた理由 / 選んだ理由
    公開のたびに本文からリンクを外す 連載は5本あり、回が進むたびに手を入れる。忘れた回だけ404が残る
    「次回」を書かない 404は消えるが、次があることも伝わらなくなる。連載として損
    表示するときに判定する 採用。書く側は常に「次回」を書ける。公開日が来れば自動でリンクに戻る

    1つめを外したのは、人が覚えていることを前提にした手順だからです。今回そもそも見落としたのは、覚えていることを前提にした確認をしていたからでした。同じ形の対策を足すと、同じ理由で破れます。

    本文を通す場所が、もう1段増えた

    このサイトの本文は、表示する前にいくつかの処理を順番に通ります。商品カードを差し込み、リンクの末尾の形を揃え、見出しに目印を付ける。そこに1段足しました。

    const posts = await getPosts();
    
    // getPosts() は公開済みのものだけを返す。
    // ここに無いスラッグは「CMSには在るが、まだ出ていない」記事になる。
    const publishedSlugs = new Set(
      posts.map((p) => p?.slug).filter((s): s is string => Boolean(s)),
    );
    
    const rawContent = softenPendingLinks(
      normalizeInternalLinks(injectProducts(post.content)),
      publishedSlugs,
    );
    

    判定の材料はもともと手元にありました。前後の記事と関連記事を出すために、記事一覧をすでに取っています。取得の順番を入れ替えただけで、通信は1回も増えていません。

    リンクを消さずに、文字にした

    return html.replace(POST_LINK, (whole, _before, slug, _after, text) => {
      if (publishedSlugs.has(slug)) return whole;
      return `<span class="pending-link">${text}<span class="pending-link-note">(近日公開)</span></span>`;
    });
    

    行き先が無いなら丸ごと消す、という手もありました。選ばなかったのは、予告そのものには意味があるからです。

    押せないことと、無いことは違う

    「次回:打ち方が違っても、同じ言葉として扱う」という一文は、続きがあることと、その内容を伝えています。押せるかどうかとは別の仕事です。

    消すと、その情報まで一緒に消えます。薄い文字にして「(近日公開)」を添えれば、押せないことは分かり、何が来るかは残ります。

    「(近日公開)」をCSSの装飾ではなく本文の一部として出しているのは、読み上げや、文字を選んでコピーしたときに落ちないようにするためです。押せない理由は、飾りではなく中身として持たせることにしました。

    置き換えで壊しそうな場所

    本文のHTMLを書き換える処理は、意図しないものまで巻き込みます。6つ試しました。

    試したこと 期待
    公開済みへのリンク そのまま残る
    1つの行に「前回」と「次回」が並ぶ 前回は残り、次回だけ文字になる
    class や title が前後に付く 拾える
    連載の目次(/series/)へのリンク 触らない
    外部サイトのURL 触らない
    リンクの文字に <strong> が入る 中のタグを保つ

    5つめは見落としやすい形をしています。外部のURLにも /blog/ という並びが現れることがあるので、行き先がサイト内かどうかを先に判定する必要がありました。

    確認そのものを直す

    今回いちばん効いたのは、コードの修正ではありません。確認の条件を書き直したことです。

    見ていたもの
    前 リンク先のスラッグが、CMSに存在するか
    後 リンク先のスラッグが、公開済みの一覧に在るか

    条件を書き直して数え直したら、公開中の57本のうち、公開前を指しているリンクは1件でした。指摘を受けたその1件です。数が合ったことで、見落としの範囲が確定しました。

    公開日が来たら、本当に戻るのか

    「公開日が来れば自動でリンクに戻る」と書きましたが、これは作った側の見込みでしかありません。確かめる機会は、2日後に来ました。

    第2回の公開日に、第1回と第2回を両方開いて、連載の案内を見比べました。

    記事 「次回」の行き先 表示
    第1回 第2回(その日に公開) リンクに戻っていた。押すと開く
    第2回 第3回(まだ予約) 文字のまま、「(近日公開)」付き

    第1回の「次回」は、2日前まで押せない文字でした。その日の朝、誰も何も触らないまま押せるリンクになっていて、代わりに第2回の「次回」が押せない文字になっていました。1本ずつ、順送りに開いていく形です。

    戻ることまで確かめて、はじめて直ったと言える

    押せなくする側だけを見て終わりにしていたら、「公開後もずっと押せないまま」という別の壊れ方に気づけませんでした。

    表示を止める仕組みは、止めたものを再開させる側まで確認してようやく半分ではなくなります。

    今回の学び

    まず、存在することと、辿り着けることは別だということ。手元のデータで「在る」と確認できても、読む人に配っている集合に入っていなければ意味がありません。確認は、読む人が見るほうの集合でやる必要がありました。

    次に、書き手の視界は広すぎること。管理画面には公開前のものも並びます。そこを基準に考えると、自分にだけ見えているものを、全員に見えていると錯覚します。

    そして、人が覚えていることを前提にした対策を足さないこと。見落としの原因が「覚えていること前提の確認」だったのに、対策も同じ形にしたら、同じ理由で破れます。表示するときに判定する形にしたのは、そこを繰り返さないためでした。

  • ソース解説(5) 文字列を自分で組み立てる場所

    ソース解説(5) 文字列を自分で組み立てる場所

    このサイトで、HTMLを文字列として自分で組み立てている場所は2つあります。RSSフィードとサイトマップです。

    画面に出るページは、部品を組み合わせれば勝手に出来上がります。この2つは違います。1行ずつ並べて、最後に文字列としてつなぎます。

    この記事で扱うこと

    自分で組み立てる場所に何が必要になるか。具体的には、エスケープ、日付の形式、そして省略していい項目とそうでない項目の見分け方です。

    1文字でフィード全体が壊れる

    まずここから。記事のタイトルに「&」や「<」が入ることは普通にあります。

    順番 元の文字 書き出す形
    1 & &amp;
    2 < &lt;
    3 > &gt;
    4 二重引用符 &quot;
    5 単引用符 &apos;

    これを通さずにタイトルを書き込むと、XMLとして読めなくなります。

    壊れ方が「1記事だけ出ない」ではない

    HTMLは多少おかしくてもブラウザが解釈してくれますが、XMLは1か所でも壊れると全体が読めません。

    つまり、タイトルに「&」が入った記事を1本出した瞬間に、購読アプリ側では何も表示されなくなります。その1本だけが抜けるのではありません。

    そして & の置き換えがいちばん最初にあるのが重要です。後ろに回すと、< を &lt; に変えた後の & をもう一度変換してしまい、&amp;lt; になります。

    日付の名前を、自分で持っている

    RSSが定める日付は「Sun, 24 Aug 2026 13:27:40 GMT」の形です。英語の曜日と月名を使います。

    const DAYS = ["Sun", "Mon", "Tue", ...];
    const MONTHS = ["Jan", "Feb", "Mar", ...];

    日付を文字にする機能はブラウザにもありますが、結果が端末の言語設定に左右されます。日本語の環境で「日」「8月」と出てしまうと、フィードとしては読めません。

    形式が決まっているものは、こちらで名前を持っているほうが確実でした。

    時間帯の表記が無い文字列

    WordPressから届く日付は「2026-08-24T13:27:40」の形です。末尾に時間帯の表記がありません。

    const iso = HAS_TIMEZONE.test(gmt)
      ? gmt
      : gmt + "Z";

    すでに時間帯が付いているかを確かめて、無ければ Z(世界標準時)を足してから解釈します。

    HAS_TIMEZONE は、末尾に時間帯の表記があるかを判定する正規表現です。実物は1行に書いてありますが、長くて読む邪魔になるので、ここでは名前を付けて抜き出しています。

    足さないと、見る人の環境で日付がずれる

    時間帯の表記が無い文字列は、解釈する側の地域として扱われることがあります。日本で動かすと9時間ずれた値になります。

    表示は普通に出ます。「少し前の日付だな」と思うだけで、ずれていることに気づけません。

    無い項目は、書かない

    組み立てる側でいちばん判断が要るのがここでした。

    pubDate ? "<pubDate>" + pubDate + "</pubDate>" : null,
    summary ? "<description>" + summary + "</description>" : null,
    

    そして最後に、null の行を落としてからつなぎます。

    .filter((line): line is string => line !== null)
    .join("\n")

    日付や説明が取れなかったとき、空のまま項目だけ書くこともできます。そうしないのは、空の項目が「値が無い」ではなく「空という値がある」と読まれるからです。

    サイトマップ側でも同じ判断をしています。

    if (post.modified) {
      entry.lastModified = new Date(post.modified);
    }
    無いときに今日の日付を入れてはいけない

    更新日が取れないときに今日の日付を埋めると、「毎日更新されている」と伝えることになります。

    形としては揃いますが、内容は嘘です。何度か見に来られて、毎回変わっていないと分かれば、そのうち信用されなくなります。書かないほうが正確でした。

    更新日時に、生成時刻を使わない

    フィード全体の更新日時も同じ考え方です。

    const lastBuildDate = toRfc822(posts[0]?.dateGmt);

    作った瞬間の時刻ではなく、いちばん新しい記事の日付を使っています。

    生成時刻にすると、中身がまったく変わっていなくても毎回新しく見えます。フィードは1時間ごとに作り直しているので、記事を1本も書いていない日でも「24回更新された」ことになります。

    作り直す間隔を、用途で分けている

    このサイトには、作り直す間隔が3種類あります。

    対象 間隔 理由
    記事ページ・一覧 5分 読む人が直接見る
    RSS 1時間 購読アプリ側も数十分おきにしか来ない
    サイトマップ 1時間 載っているかだけが問題

    RSSを5分にしても、届く速さは変わりません。相手が見に来る間隔のほうが長いからです。短くして得があるのは、読む人が直接開くページだけでした。

    サイトマップは、XMLを書いていない

    最後に1つ。フィードは文字列を組み立てていますが、サイトマップは違います。

    export default async function sitemap():
      Promise<MetadataRoute.Sitemap> {
      return [...staticEntries, ...glossaryEntries, ...postEntries];
    }

    配列を返すだけで、XMLは自動で作られます。RSSに同じ仕組みが無いので、そちらだけ手で書いています。

    手で書ける場所では、手で書かない。エスケープも日付の形式も、自分で持てば自分で間違えられるようになります。

    今回の学び

    まず、厳しい形式ほど、壊れ方が全か無かになるということ。XMLは1文字で全体が読めなくなります。多少おかしくても表示されるHTMLに慣れていると、この差は見落とします。

    次に、値が無いときに、形を揃えるために埋めないこと。更新日が無いのに今日の日付を入れると、形は整いますが意味が変わります。空欄のまま出すか、項目ごと書かないかを選ぶほうが正確でした。

    そして、間隔は相手の都合で決めること。購読アプリが1時間おきにしか来ないなら、こちらが5分ごとに作り直しても誰も早く受け取れません。速くする対象を選ぶのも、相手が誰かを見てからでした。

  • そのページは何だと名乗っているか

    そのページは何だと名乗っているか

    用語集の分類ページを調べていて、妙な指定に気づきました。このページは「記事」だと名乗っていました。

    中身は用語が並んだ一覧です。公開日もなければ、書いた人もいません。それでも、SNSや検索エンジンに向けては「これは1本の読み物です」と伝えていました。

    この記事で扱うこと

    ページが自分を何だと名乗っているか、という話です。名乗りが実態とずれていた2ページと、そもそも何も名乗っていなかった4ページを直しました。

    既定値をそのまま連れてきていた

    SNSで共有されたときの見え方を決める指定に、type という項目があります。1本の記事なら article、そうでないページは website です。

    用語集の分類ページには、こう書いてありました。

    openGraph: {
      type: "article",
      title,
      description: cat.description,
    }

    記事ページの書き方をそのまま持ってきた形跡です。動かして確かめる対象ではないので、間違っていても画面には何も出ません。

    article が意味していること

    article は「公開日と書き手を持つ読み物」を指します。つまり、この指定を書いた時点で「いつ書かれたか」「誰が書いたか」があるものとして案内していることになります。

    実際にはどちらも無いので、受け取る側は探しに行って見つけられません。嘘をついているというより、名乗りだけが先に立っている状態です。

    同じ書き方がプライバシーポリシーにもありました。こちらも読み物ではありません。2ページとも website に直しました。

    何も名乗っていないページが4つあった

    ついでに構造化データを数えました。検索エンジンに「このページは何か」を直接伝える仕組みで、画面には出ません。

    ページ 直す前
    記事ページ BlogPosting + パンくず
    ブログ一覧 無し
    用語集 無し
    プロフィール 無し
    プライバシーポリシー 無し

    記事ページだけ揃っていて、それ以外は空でした。記事を書くときに考えたことが、そのとき触っていたファイルの中で完結していたわけです。

    記事用の部品を広げるか、別に作るか

    足すにあたって、記事用の部品を使い回すか迷いました。

    使い回そうとすると、日付の扱いで詰まります。記事には公開日があり、一覧にはありません。「日付が無いときは書かない」という枝を足すことになります。そのうち書き手についても同じ枝が必要になり、部品の中身が条件分岐で埋まっていきます。

    名乗る内容が違うものは、別の部品にする

    結局、一覧用に別の部品を作りました。記事は BlogPosting、一覧は CollectionPage、プロフィールは ProfilePage を名乗ります。

    同じ処理をしているように見えても、言っている内容が違うものは、混ぜると条件分岐になります。分けておけば、それぞれが1つのことだけを言います。

    パンくずは全ページに付けました。検索結果に出る階層表示に使われるもので、画面上の階層と一致していないと逆効果になります。実際のURLの形をそのまま写しただけです。

    ドメイン名が4か所に書いてあった

    作業中に、別の問題が見えました。サイトの絶対URL https://www.kisaku.site が、4つのファイルにそれぞれ書いてありました。

    robots.txt を作るところ、サイトマップを作るところ、RSSを作るところ、構造化データを作るところです。共通しているのは、どれも画面に出ない場所だということでした。

    ページの head に入れる相対パスは、設定してある基準URLが絶対URLへ直してくれます。直してくれないのは、この4か所のように文字列を自分で組み立てている場所だけです。

    1か所にまとめて、そこから読む形にしました。ドメインを変える日に1つ書き換え忘れても、画面を見ているかぎり気づけない種類の値だからです。

    今回の学び

    まず、ページは自分が何であるかを名乗っているということ。画面に出ない場所で、公開日や書き手を持つ読み物だと宣言していました。表示は正しく、指定だけが実態とずれていました。動かして確かめる対象ではないので、読みに行かないかぎり分かりません。

    次に、書き方をコピーすると、意味もコピーされること。記事ページの指定を持ってきたのは自然な流れですが、その中には「これは記事です」という宣言も含まれていました。形だけ借りたつもりで、中身まで一緒に来ていたわけです。

    そして、条件分岐が増えそうなときは、分ける合図だと考えること。1つの部品で両方をまかなおうとすると「日付が無いとき」「書き手が無いとき」の枝が並びます。それは、そもそも別のことを言おうとしている合図でした。

  • 見た目で区別が付いているものは、機械には区別が付いていない

    見た目で区別が付いているものは、機械には区別が付いていない

    ブログ一覧を見て、見出しの構造を確かめました。ページの題名にあたる見出しが、1つもありませんでした。

    画面ではちゃんと「ブログ」と大きく出ています。その下に記事の題名が並んでいます。誰が見ても、どちらが上でどちらが中身かは分かります。分からないのは、字の大きさが見えない側だけでした。

    この記事で扱うこと

    見た目で伝わっている区別が、機械には伝わっていなかった話です。何が起きていたか、どう直したか、そして「見えるから伝わっている」という思い込みについて書きます。

    全部が同じ格で並んでいた

    見出しには段階があります。h1 がそのページの題名、h2 がその中の章、というように、番号が入れ子の深さを表します。

    一覧ページを調べたときの中身は、こうなっていました。

    画面での見え方 実際の見出し
    ブログ(大きい文字) h2
    記事の題名 h2
    記事の題名 h2
    記事の題名 h2

    h1 はどこにもありません。そして「ブログ」と個々の記事が、同じ h2 で横並びになっていました。

    画面上は、文字の大きさが違うので迷いません。「ブログ」は太くて大きく、記事の題名はそれより小さい。人は大きさの差を、上下関係として読み取ります。

    大きさは、機械には渡っていない

    文字の大きさは見た目の指定であって、構造の指定ではありません。読み上げソフトが見出しをたどるとき、参照するのは番号のほうです。

    番号が同じなら同じ格です。したがって、たどっていくと「ブログ」も1本の記事も、並列の項目として現れます。このページが何のページなのかを名乗る見出しが、存在しないことになります。

    見出しは飾りではなく、目次として使われる

    見出しに番号を付ける意味は、画面を上から順に見られない人にとってはっきり出ます。

    読み上げソフトには、見出しだけを拾って移動する機能があります。本文を全部聞かずに、章の一覧から目的の場所へ飛べます。見出しの番号は、そのまま目次の階層になります。

    階層が平らだと、この目次は「同じ深さの項目が延々と並んだもの」になります。どれがページの題名で、どれが中身なのかが分かりません。全部を聞いて中身から判断することになります。

    検索エンジンから見ても事情は似ています。ページの主題を示す位置に何も無く、記事の題名だけが同じ格で並んでいる状態です。

    直すのは1か所ではなかった

    見出しは共通の部品で描いていました。ここが便利であると同時に、問題が広がった理由でもあります。

    部品の中で h2 を決め打ちしていたので、どのページで使っても必ず h2 になります。ページの題名として使いたい場合でも h2 でした。

    そこで、部品の側に「どの格で出すか」を渡せるようにしました。既定は今までどおり h2 のままにして、ページの題名として使うところだけ h1 を指定します。

    <SectionTitle as="h1">ブログ</SectionTitle>

    見た目は変えていません。字の大きさも余白もそのままです。変えたのは、機械が読む側の情報だけです。

    他のページも同じだった

    1か所で起きているなら、他でも起きています。全ページを取り寄せて h1 の数を数えました。

    ページ 直す前 直した後
    トップ 1つ 1つ
    ブログ一覧 0 1つ(ブログ)
    用語集 0 1つ(用語集)
    用語集の分類 0 1つ(分類名)
    プロフィール 0 1つ(プロフィール)
    プライバシーポリシー 0 1つ(ポリシー名)

    トップだけが正しく、残りは全滅でした。理由も同じで、トップは共通部品を使わずに直接書いていたからです。共通化した側だけが揃って間違っていました。

    数えれば分かる種類の不具合

    この確認は難しくありません。各ページのHTMLを取り寄せて、h1 がいくつあるかを数えるだけです。0でも2以上でも、何かおかしい合図になります。

    難しかったのは、確かめようと思うことのほうでした。画面上は最初から正しく見えていたので、疑う理由がありませんでした。

    今回の学び

    まず、見た目で付けた区別は、見た目の外には出ないということ。字を大きくすれば人には上位だと伝わりますが、それは番号にも構造にも変換されません。人に伝わっているかどうかと、機械に伝わっているかどうかは別に確かめる必要がありました。

    次に、共通部品は、正しさも間違いも同じだけ広げること。見出しの部品を1つにまとめたおかげで、直すときは1か所で済みました。同じ理由で、間違いも5ページに均等に配られていました。まとめた部分ほど、最初の設計を疑う価値があります。

    そして、「壊れていない」と「正しい」は違うということ。h1 が無くてもページは表示され、リンクも動き、誰も困った報告をしません。症状の出ない不具合は、症状を待っていても見つかりません。数えて確かめるまで、無いことに気づけませんでした。

  • 一部だけ足したつもりが、丸ごと置き換わっていた

    一部だけ足したつもりが、丸ごと置き換わっていた

    1行足したつもりでした。実際には、そのページの共有画像が消えていました。

    しかも消えたのは、その日に触ったページだけではありません。調べたら、以前から9ページで同じことが起きていました。画面には出ず、動作も壊れず、誰も気づいていませんでした。

    この記事で扱うこと

    Next.js のページ情報(メタデータ)は、親と子で「項目ごとに混ざる」のではなく「かたまりごとに入れ替わる」という話です。気づいた経緯と、同じ間違いを繰り返さないための置き方を書きます。

    正規URLを足しに行っただけだった

    きっかけは検索エンジン側の指摘でした。ブログ一覧に絞り込みが付いているため、同じ中身が複数のURLで見える状態になっていて、56件が「重複しています」と報告されていました。

    対処として、各ページに「このページの代表URLはこれです」という指定を足しました。Next.js では alternates というかたまりの中に書きます。

    // 記事一覧ページ
    export const metadata = {
      alternates: {
        canonical: "/blog",
      },
    };

    足したのは1項目です。ところが公開後に確認すると、そのページからRSSフィードの場所が消えていました。

    親に書いてあったものが、丸ごと消えた

    フィードの場所は、全ページ共通の設定として親の側に書いてありました。

    // 全ページ共通の側
    alternates: {
      types: {
        "application/rss+xml": [{ url: "/feed.xml", title: "..." }],
      },
    },

    期待していたのは、親の types はそのまま残り、子で足した canonical が加わることです。実際は違いました。

    入れ替わる単位は「項目」ではなく「かたまり」

    子のページが alternates を書くと、親の alternates はまるごと捨てられます。中の項目どうしは比べられません。

    つまり canonical だけを書いた瞬間、そのページの alternates は「canonical しか無いもの」になります。フィードの場所は、書き足したのではなく、上書きで消えたことになります。

    購読アプリはブログ一覧を最初に見に来ます。そこから場所が消えるのは困ります。

    同じ形が、すでに別の場所で起きていた

    この仕組みが分かった時点で、嫌な予感がしました。同じ書き方をしているかたまりが、もう1つあるからです。SNSで共有されたときの見え方を決める openGraph です。

    共有画像は親の側に1枚だけ置いてあり、各ページはそこに乗るはずでした。ところが個別のページには、こう書いてありました。

    export const metadata = {
      openGraph: {
        title: "...",
        description: "...",
      },
    };

    画像の指定はありません。画像を消すつもりも、当然ありません。それでも alternates と同じ理屈で、親の openGraph はまるごと捨てられます。画像だけが残るということは起こりません。

    公開中のページを1つずつ取り寄せて、実際のHTMLを見ました。

    ページ og:title og:image
    トップ あり あり
    ブログ一覧 あり 無し
    用語集 あり 無し
    プロフィール あり 無し
    各記事ページ ほか あり 無し

    合計9ページで画像が出ていませんでした。og:title は書いてあるので、確認する側から見ると「設定してある」ように見えます。抜けているのは、書いていない項目のほうでした。

    なぜ長いあいだ気づかなかったのか

    この不具合には、気づくきっかけがほとんどありません。

    画面には何も出ません。ページは普通に表示され、リンクも動きます。ビルドも通ります。書き間違いではないので、警告も出ません。「書いていないものが消える」ので、書いたものを見ているかぎり異常が見えません。

    見えるのは自分の外側だけ

    この設定が効くのは、リンクを他のサービスに貼ったときです。共有されて初めて、画像の無い地味なリンクとして出ます。

    つまり自分のサイトを見ているかぎり永久に分かりません。今回も、別件を調べていて仕組みのほうから逆算しただけで、症状から見つけたわけではありません。

    書き写すのではなく、1か所から読む

    直し方は単純で、上書きするページでは、消えるものを毎回書き添えます。

    ただし各ページに同じ内容を書き写すと、いずれ書き忘れます。今回消えていた9ページは、まさに書き忘れの集まりでした。同じ直し方をすれば、同じことが起こります。

    そこで、共通の値を1か所に置いて、そこから読む形にしました。

    export const FEED_ALTERNATE_TYPES = { /* ... */ };
    
    export function alternatesFor(canonicalPath) {
      return {
        canonical: canonicalPath,
        types: FEED_ALTERNATE_TYPES,
      };
    }

    各ページは alternatesFor("/blog") と書きます。「canonical を足す」と書けば、消えるはずのものが自動で付いてきます。共有画像のほうも同じ形にして、既定の画像を1か所に置き、上書きするページはそれを混ぜて返すようにしました。

    仕組みは変えられないので、書き方のほうを変える

    「かたまりごと入れ替わる」という挙動そのものは、こちら側では変えられません。変えられるのは、上書きが必要なときに安全な形しか書けないようにしておくことです。

    注意して書き写す運用は、9ページぶん失敗した実績があります。

    今回の学び

    まず、設定を足すときは「入れ替わる単位」を先に確かめるということ。項目ごとに混ざると思い込んでいたので、1項目を足す作業のつもりでいました。実際にはかたまりの差し替えでした。足し算だと思っていた操作が、代入だったわけです。

    次に、1件見つけたら、同じ形を探しに行くこと。今回いちばん役に立ったのは、直したことではなく「同じ書き方をしている場所は他にないか」と考えたことでした。目に見える不具合は1つでしたが、同じ形をした見えない不具合が9つありました。

    そして、自分の画面に出ない設定は、外から取り寄せて確かめること。共有時の見え方も、検索エンジン向けの指定も、開発中の画面には出てきません。公開中のHTMLを1ページずつ取り寄せて初めて、書いてあるつもりのものが無いと分かりました。書いたコードではなく、出ている結果を見る必要がありました。

  • 見つけてもらうために、書き方は変えない

    見つけてもらうために、書き方は変えない

    このサイトで検索まわりにやってきたことを、順番に並べてみます。

    先に立場を書いておくと、順位を上げる方法は扱いません。やってきたのは「存在を知らせる」「探している人の言葉に近づける」の2つだけで、どちらも順位を操作する話ではありません。

    この記事で扱うこと

    何を先にやったかという順番、記事の見出しと検索結果の文字を分けた判断、そして「やらないと決めたこと」です。

    最初にやったのは、存在を知らせること

    公開したばかりのサイトには、外部からのリンクがありません。検索エンジンはリンクをたどってページを見つけるので、誰からもリンクされていないサイトは、そもそも見つけてもらえません。

    そこで最初に用意したのは3つです。

    用意したもの 役割
    sitemap.xml ページの一覧を直接渡す
    robots.txt 見に来てよい範囲を伝える
    構造化データ ページの意味を機械が読める形で示す

    あわせてSearch Consoleに登録しました。ここでつまずいた話は別記事にしていますが、要点はドメインを買った会社の管理画面と、実際にDNSを答えているサーバーが別だったことです。設定は保存できるのに、誰も参照しない。エラーも出ない。丸一日かかりました。

    順位ではなく、到達

    これらは順位を上げるものではありません。見つかるまでの時間を短くするものです。

    存在を知られていない状態で書き方の工夫をしても、読む相手がいません。順番として、先にこちらでした。

    次にやったのは、記事どうしをつなぐこと

    記事が20本を超えたあたりで、1本読んだ人が次に何を読むかという導線が無いことに気づきました。

    やったことは3つです。

    ひとつめは用語集です。116語をまとめ、記事の末尾から「この記事に出てくる用語」として繋ぎました。これは検索対策として始めたものではなく、初学者にも読めるようにするためのものです。ただ結果として、記事どうしが用語を介してつながりました。

    ふたつめは関連記事です。以前は「1つ新しい記事」と「1つ古い記事」しか出していませんでしたが、公開日が近いことと内容が近いことには関係がありません。タグの重なりで選ぶように変えました。

    みっつめはページの統合です。実績とスキルの個別ページは、トップページの同名セクションとほぼ同じ内容でした。同じ内容が2つのURLにあると、検索エンジンにどちらを出すか選ばせることになります。統合して、旧URLは転送しました。

    そして、タイトルを2つに分けた

    ここが今回いちばん考えた部分です。

    このサイトの記事の見出しは、内容を説明するのではなく、含みを持たせて付けています。

    これまでの見出し
    速くしたら、壊れた
    所有権をどう証明するか
    「読み終えた」をどう数えるか
    ゆっくり読む人ほど、読了にならなかった

    読んだあとに何かが残るように、と思って付けています。

    ところが検索する人は、この言葉では探しません。「所有権をどう証明するか」で検索する人はいませんが、「Search Console 所有権確認 DNS」で困っている人はいます。中身はまさにそれなのに、言葉が噛み合っていませんでした。

    見出しを書き換える案は、やめた

    検索語を入れた説明的な見出しに直せば、噛み合います。しかしそれをすると、読み物としての性格が変わります。

    このサイトは、作業の記録であると同時に、判断の記録として書いています。見出しはその入口なので、そこを検索エンジンに合わせて書き換えるのは、目的と手段が入れ替わっている気がしました。

    場所によって、見られ方が違う

    検索結果のリンクは「探している言葉と一致するか」で開かれます。SNSで共有されたリンクは「面白そうか」で開かれます。

    見られ方が違うなら、出す文字も違っていい。そう整理したら、答えが出ました。

    見出しはそのままにして、検索結果に出す文字だけを別に持つことにしました。SNSで共有されたときは見出しのほうを出します。

    出る場所 出す文字
    検索結果 Search Consoleの所有権確認をDNSのTXTレコードで行う
    ページの見出し・SNS 所有権をどう証明するか

    文字数も測った

    あわせて、検索結果に出る幅も測りました。目安は全角30文字ほどです。

    サイト名の接尾辞「| KiSAKU Portfolio」が全角9.5文字ぶんを毎回占めていて、公開中21本のうち14本がこの幅を超えていました。接尾辞を記事ページから外すだけで、超えるのは7本に減りました。

    サイト名は検索結果のURL表示にも出ています。タイトルの中で場所を取ってまで繰り返す必要は、薄いと判断しました。

    やらないと決めたこと

    逆に、やらないことも決めました。

    検索語を狙って記事を量産すること。書いていないことは書けません。このサイトの記事は、実際に自分がつまずいて調べたことだけで成り立っています。検索語から逆算して書き始めると、その前提が崩れます。

    用語を1語1ページにすること。個別ページのほうが検索に有利、という一般論はあります。しかし1語400字のページを100枚作ると、中身の薄いページが多いサイトと見なされる可能性がありました。この規模では逆効果と判断して、分野ごとにまとめています。

    広告を置ける場所すべてに置くこと。内容と無関係な記事に広告を挟むと、読んだ人の信頼を削ります。掲載できる場所の数と、掲載すべき場所の数は違います。

    一般論が、自分の規模で成立するとは限らない

    「個別ページのほうが強い」「記事数が多いほうが有利」——どれも間違いではありませんが、条件付きの正解です。

    条件を確かめずに無条件の正解として受け取ると、規模が合わないまま実行して逆効果になります。

    まだ測っていない

    正直に書くと、ここまでの効果はまだ数字で確かめていません。

    Search Consoleに登録したのが最近で、記事の公開も始めたばかりです。いま順位やクリック数を見ても、変化なのか誤差なのか区別がつきません。

    やったことを記録しておいて、数字が溜まってから振り返るつもりです。効果を確かめる前に「効果があった」と書かないことも、この記録の一部だと思っています。

    今回の学び

    まず、順番があること。存在を知られていない段階で書き方を工夫しても、読む相手がいません。まず見つかる状態を作り、それから中身の噛み合わせを直す。逆にすると、労力の割に何も起きません。

    次に、探している人の言葉と、書いた人の言葉は違うこと。良い見出しを付けたつもりでも、それが検索で使われる言葉とは限りません。両立しないなら、場所ごとに出す文字を変える手があります。

    そして、一般論は条件付きの正解だということ。規模や状況が違えば逆に働きます。自分の数字に当てはめてから採用するべきです。

    最後に、確かめる前に成果を語らないこと。やったことと、効いたことは別です。記録として残す価値があるのは、前者を正確に書いておくことのほうだと思います。

  • 登録してから、広告を出すまで

    登録してから、広告を出すまで

    このサイトで、成果報酬型の広告を扱えるようにしました。

    手続きそのものは難しくありません。順番を間違えると手戻りになるところがいくつかあったので、そこを中心に残しておきます。

    この記事で扱うこと

    登録から掲載までの流れと、着手する順番。そして「何を紹介するか」を先に決めておくべき理由です。

    全体の流れ

    順番
    1. 掲載するサイトを整える     ← 審査で見られる
    2. ASPに登録する(サイト審査)
    3. 広告主ごとに提携を申し込む   ← 1件ずつ
    4. 承認される
    5. リンクタグを取得する
    6. 記事に置く
    7. 広告であることを表示する

    時間がかかるのは2と4です。どちらも相手の審査待ちなので、こちらからは進められません。待っている間に何をやっておくかで、承認後の速さが変わります。

    先にやっておくべきだったこと

    プライバシーポリシー

    これが最初です。広告を出すかどうかに関係なく、アクセス解析でCookieを使い、問い合わせフォームで個人情報を受け取っているなら、すでに必要になっています。

    広告の話が出てから慌てて作ることになりました。順番としては、サイトを公開した時点で用意しておくものです。

    広告であることを示す仕組み

    2023年10月から、広告を広告と分かる形で示すことが必要になりました。書き忘れると規制に触れます。

    本文に手で書く方法もありますが、手で書くものは、いつか書き忘れます。記事にタグを付けたら自動で表示が出る形にしておくと、忘れようがなくなります。

    これは提携が決まる前に作れます。実際、先に作っておいたので、承認後は記事にタグを付けるだけで済みました。

    何を紹介するかを決める

    ここがいちばん大事だと思います。「実際に使っているものだけ」と先に決めました。

    使っていないものを紹介できない理由

    このサイトは、書いた人が何を考えて作ったかを読んでもらう場所です。使ったことのないものを勧めた瞬間、他の記事も同じ調子で書かれていると思われます。

    1件の報酬と、全記事の信頼が釣り合いません。

    この条件で残るものは、多くありません。実際に契約しているサービスのうち、扱いのあるものだけになります。選択肢が減るのは、条件が効いている証拠だと考えることにしました。

    1つの広告主を、複数の記事に置くとき

    紹介できるものが少ないと、同じ広告主を複数の記事に置くことになります。ここで手を抜くと、同じ文面が何ページにも並びます。

    読者からは「どの記事にも同じ広告が挟まっている」としか見えません。検索エンジンから見ても、同一の内容が複数ページに散っている状態です。

    そこで、記事ごとに違うことを書く形にしました。企画の記事には選定の基準を、構築の記事にはつまずいた点を、公開の記事には契約前に知りたかったことを。どれもその記事で扱っている内容の延長線上にあります。

    この形なら、追記そのものが記事の情報量を増やします。

    置かない記事を決める

    候補に挙げた記事のうち1本を外しました。URLの処理に関する記事で、ドメインにもサーバーにも触れていません。

    ここに広告を置くと、内容と無関係な広告を挟んだことになります。読んだ人の信頼を削る割に、クリックもされないはずです。

    置ける場所すべてに置かない

    掲載できる場所の数と、掲載すべき場所の数は違います。文脈が合わないところに置くと、合っている場所の説得力まで下がります。

    収益の見込みについて

    正直に書くと、この規模のサイトで大きな金額にはなりません。

    それでも意味があると考えたのは、仕組みを一度通しておくと、判断の材料が手に入るからです。何がクリックされ、何がされないか。どの記事から動きがあるか。これは推測では分かりません。

    そして、こうして手順と判断を書き残せます。

    今回の学び

    まず、待ち時間のある手続きは、待つ前に準備を終えること。審査は自分では進められません。その間にできることを先に片付けておくと、承認された日に掲載できます。

    次に、条件を先に決めておくこと。「実際に使っているものだけ」を後から決めると、すでに置いたものを見直すことになります。基準は、選ぶ前に決めるから機能します。

    そして、忘れうることは、忘れられない形にすること。広告の表示は、書き忘れが規制に触れます。手で書く運用にした時点で、いつか漏れます。

    最後に、置ける場所すべてに置かないこと。数を増やすほど1件あたりの説得力は下がります。合う場所にだけ置くほうが、結果として届きます。

  • 分類は、探す人の手がかりではない

    分類は、探す人の手がかりではない

    用語集が116語になりました。分野ごとに8ページへ分けてあります。

    ある日、自分で「WAFってどこに書いたっけ」と探して気づきました。分野を当てないと、たどり着けません。

    この記事で扱うこと

    分類が誰のためのものかという話と、検索をブラウザ側に置いた判断、そして「打ち方の違いで見つからない」を潰すための文字のならし方です。

    分類は、書く側の都合でできている

    8つの分野は、書くときには役に立ちました。どこに何を書くかが決まり、抜けにも気づけます。

    しかし探す側から見ると、事情が違います。

    WAFを調べたい人がやること
    用語集を開く
      ↓
    8つの分野から、それらしいものを選ぶ  ← ここで当て推量
      ↓
    違ったら戻ってやり直す

    調べたい語が決まっている人にとって、分類は遠回りです。「WAFはセキュリティの話だ」と分かっている人は、そもそもWAFを調べません。

    整理した本人にしか使えない整理

    分類は、作った人には自明です。だから使いにくさに気づきません。自分で探して詰まって初めて、これは自分用の並べ方だったと分かりました。

    検索を、ページの先頭に置いた

    入力するとその場で絞り込まれ、押せば該当の語まで直接飛びます。分野の一覧は、検索していないときだけ出します。

    絞り込んだ結果の下に一覧が残っていると、どこまでが結果なのかが分からなくなるためです。

    打ち方の違いで、見つからないようにしない

    ここに手間をかけました。同じ言葉でも、打ち方は人によって違います。

    入力 探しているもの
    キャッシュ / きゃっしゅ / キャッシュ キャッシュ
    waf / WAF / WAF / わふ WAF
    resize / リサイズ ResizeObserver

    これを別物として扱うと、「あるのに見つからない」という結果になります。本人からすれば、無いのと同じです。

    比べる前に、全角と半角、大文字と小文字、カタカナとひらがなを一方に寄せています。読み(ひらがな)と、URLに使っているローマ字のIDも検索対象に含めました。日本語入力では変換の途中でも文字が届くので、確定前のひらがなでも当たるようにしてあります。

    なぜブラウザ側で絞り込むのか

    検索といえばサーバーに問い合わせる形が思い浮かびますが、今回は全部のデータを最初に渡してしまう形にしました。

    渡すもの
    語 + 読み + 1行の説明 + 置き場所  × 116語
    = 十数キロバイト(記事1本の本文より小さい)

    この量なら、最初に渡しても負担になりません。1文字打つたびに通信する必要がなくなり、待ち時間がゼロになります。

    規模が変われば、答えも変わる

    これは116語だから成立する判断です。数千語になれば、最初に渡す量が重くなり、サーバー側で絞り込む形が正しくなります。

    いまの規模で正しいことを選び、変わったら選び直すほうが、最初から大きく作るより速く進めます。

    残った引っかかり

    ひとつ、きれいでない点があります。用語の本文はCMS、検索用の索引はコードと、置き場所が分かれました。

    語を1つ足すには、両方を更新する必要があります。片方だけ直すと食い違います。

    いまは116語で、更新も自分1人なので回せます。ただしこれは「気を付ける」で運用している状態で、いつか間違えます。数が増えるか、更新する人が増えたら、索引をCMSから作る形へ変えるつもりです。

    今回の学び

    まず、分類は書く側の道具で、探す側の道具ではないこと。整理した本人には自明なので、使いにくさに気づけません。自分で探して詰まるまで分かりませんでした。

    次に、「あるのに見つからない」は、無いのと同じこと。打ち方の違いは利用者の間違いではありません。揃えるのは、こちら側の仕事です。

    そして、いまの規模で正しいものを選ぶこと。将来を見越して大きく作ると、そのぶん今日が遅くなります。変わったときに選び直せる形にしておけば十分です。

    最後に、気を付けて運用している場所は、書き留めておくこと。本文と索引が分かれている件は、いま把握しているから回るだけです。忘れた頃に食い違います。

  • 最初の画面に、本文が無かった

    最初の画面に、本文が無かった

    記事ページを開いたときに何が見えているかを、実際に測りました。

    測ったもの 値
    画面の高さ 723px
    本文が始まる位置 772px
    先頭のアイキャッチ画像 455px

    最初の画面に、本文が1文字も出ていませんでした。タイトルと日付と画像で埋まっていて、文章はスクロールしないと始まりません。

    この記事で扱うこと

    アイキャッチを消した理由、コードブロックにコピーボタンを付けた理由、見出しに個別リンクを足した理由、そして関連記事の選び方を日付から変えた理由です。

    1. アイキャッチを、記事ページから消した

    455pxは画面の6割です。ここを何が占めているかを考え直しました。

    このサイトのサムネイルは、記事タイトルを文字として描いた画像です。つまり、すぐ上の見出しと同じ内容を、画像でもう一度見せていたことになります。

    同じことを二度言うために、画面の6割を使っていた

    検索から来た人は「探している答えがここにあるか」を数秒で判断します。その数秒に見えているのがタイトルの繰り返しでは、判断する材料がありません。

    画像そのものは、記事一覧のカードとSNSで共有されたときの表示に使っています。作るのをやめたのではなく、本文を読む画面には置かないという判断です。

    2. コードブロックに、コピーボタンを付けた

    ある記事にはコードブロックが25個ありました。コマンドや設定値は、読むためではなく手元で使うために載せています。

    選択してコピーもできますが、折り返しのある長い行では取りこぼしやすい。ボタンを1つ足すだけで、この摩擦が消えます。

    ひとつ決めたのは、クリップボードが使えない環境ではボタンを出さないことです。押しても何も起きないボタンは、無いより悪いので。

    3. 見出しに、その見出しへのリンクを足した

    見出しが12個ある記事で、「この部分を見てほしい」と人に伝える手段がありませんでした。記事のURLしか送れず、相手は自分で探すことになります。

    用語集では語ごとに直接指せるよう整備したのに、記事側は未対応でした。同じ問題です。

    実装は、目次を組み立てる処理の中で一緒に埋め込みました。表示のあとにブラウザ側で差し込むこともできますが、そのぶん読み込むJavaScriptが増えます。すでに文字列を組み立てている場所があるなら、そこで済ませるほうが軽い。

    4. 関連記事を、日付ではなく内容で選ぶ

    これまで記事の末尾には「1つ新しい記事」と「1つ古い記事」だけを出していました。

    公開日が近いことと、内容が近いことには関係がありません。直前に書いた記事がまったく別の話題、ということは普通に起きます。

    タグの重なりを2点、カテゴリの重なりを1点として、上位3件を出すようにしました。

    なぜタグを重く見るか
    カテゴリ: 技術系 … ほぼ全記事が同じ
    タグ    : dns, seo, nextjs … 話題が分かれる

    カテゴリだけで判断すると、ほぼ全記事が等しく「関連」になります。粗い区分は、近さを測る物差しになりません。

    数を揃えるために、関係ない記事を混ぜない

    重なりが1つも無い記事は出しません。3件に満たなければ、その数だけ出します。1件も無ければ、セクションごと出しません。

    「関連記事」という見出しは、書いてあることが本当である前提で読まれます。体裁のために埋めると、その前提が崩れます。

    今回の学び

    まず、自分のページを、来た人の目で測ること。作っている側は上から下まで知っているので、最初の画面に何が見えているかを意識しません。数字にすると一目で分かります。

    次に、同じ内容を二度見せていないかを疑うこと。タイトルの画像とタイトルの文字は、別々に作ったので気づきませんでした。並べて初めて重複だと分かります。

    そして、片方で解決したことは、もう片方でも起きていること。用語集では直接指せるように整備したのに、記事では手つかずでした。同じ問題は、場所を変えて何度も出てきます。

    最後に、物差しが粗いと、何も測れないこと。全記事が同じ値を持つ項目で並べ替えても、順番は決まりません。分類は、細かさがそろって初めて役に立ちます。

  • ページを消すとき、一緒に消えるもの

    ページを消すとき、一緒に消えるもの

    実績とスキルの個別ページを、トップページの中へ統合しました。

    作業自体は「セクションにidを付けて、ページを2枚消す」だけです。消すほうに、決めることが集まっていました。

    この記事で扱うこと

    個別ページを持つ価値をどう判断したか、ページを消すときに一緒に消えてしまうもの、そして統合したあとに出た「リンクを押しても動かない」問題です。

    同じ内容が、2つのURLにあった

    トップページには、もともと実績・スキル・プロフィールのセクションがあります。そのうえで /projects と /skills という個別ページも持っていました。

    個別ページを持つ価値は、そこにしかない情報があるかで決まります。実績が20件あって絞り込みや並べ替えが要るなら、独立したページに意味があります。

    しかし実績は数件で、当面増える見込みもありません。結果として、ほぼ同じ内容が2か所にある状態でした。

    同じ内容が2つあると、選ばせることになる

    検索エンジンから見ると、似た内容のページが2つあります。どちらを検索結果に出すべきかを判断させることになり、評価も2つに分かれます。

    読む人にとっても、メニューの「実績」を押した先が、直前まで見ていたトップの内容とほぼ同じ、というのは肩透かしです。

    消すと、一緒に消えるもの

    ここが本題です。ページを削除すると、そのURLは404になります。失われるのは中身だけではありません。

    URLに付いていたもの 削除したとき
    検索エンジンからの評価 引き継がれず消える
    他サイトから貼られたリンク 404へ向かう
    ブックマーク・履歴 404が出る

    URLは外から指される名前です。中を整理した都合で、外からの参照を無効にしてよい理由にはなりません。

    そこで、旧URLを新しい位置へ転送する設定を入れました。

    転送の設定
    /projects  →  /#projects
    /skills    →  /#skills

    これで評価は転送先へ引き継がれ、古いリンクを踏んだ人も目的の場所に着きます。

    恒久か、一時的か

    転送には「もう戻らない」と「一時的に別の場所を見せている」の2種類があります。今回は戻す予定が無いので恒久を選びました。

    恒久転送は、ブラウザが覚える

    恒久として返すと、ブラウザはその対応を記憶します。次からはサーバーに聞かずに転送先へ行きます。速い代わりに、あとで気が変わっても、記憶を持っている人には届きません。

    迷いがあるうちは一時的な転送にしておくほうが安全です。恒久にするのは、本当に戻さないと決めてからで遅くありません。

    sitemapからは外した

    細かい点ですが、転送するURLを一覧に載せたままにしませんでした。

    sitemapは「このURLを登録してほしい」という一覧です。そこに転送するURLを書くと、「登録してほしい」と「そこへ行くな」を同時に伝えることになります。矛盾した指示は、送らないほうが良いはずです。

    統合したら、リンクが動かなくなった

    ここで問題が出ました。トップページ以外から「実績」を押しても、目的の場所へ行きません。

    原因は、ページの移動の仕方でした。

    同じ「#つきリンク」でも、起きることが違う
    同じページの中     → ブラウザが自分でスクロールする
    別のページから来た → 画面を読み込み直さず中身だけ差し替える
                         = ブラウザは「移動した」と思っていない
                         = URLの # を見て動いてくれない

    加えて、移動した直後は画面の先頭に戻されます。何もしないと「トップページの一番上に着いただけ」という結果になります。

    そこで、移動後にURLの # を読んで、その位置までスクロールさせる処理を足しました。飛び先がまだ描画されていない場合に備えて、1回だけ待って再試行しています。

    仕組みが変わると、当たり前が当たり前でなくなる

    # つきのリンクは、HTMLの最初期からある機能です。動いて当然と思っていました。

    しかし「画面を読み込み直さずに中身を差し替える」作りにした時点で、ブラウザが担当していた仕事の一部を、こちら側が引き取っています。引き取ったことに気づいていないと、こういう抜けになります。

    着地の位置も直した

    もうひとつ。アンカーで飛んだ先が、貼り付いたヘッダーの裏に隠れていました。

    飛び先の位置を計算するとき、ブラウザはヘッダーの存在を考慮しません。要素の上端を画面の上端に合わせるだけです。

    飛んだときだけ余白を確保する指定があるので、ヘッダーの高さぶんを足しました。

    今回の学び

    まず、個別ページを持つ価値は、そこにしかない情報があるかで決まること。ページ数が多いこと自体には価値がありません。同じ内容が複数の場所にあると、評価も注意も分散します。

    次に、URLは外から指される名前なので、中の都合で無効にしないこと。これはアンカーIDのときと同じ話でした。指す側は、指される側の事情を知りません。消すなら、行き先を用意してから消します。

    そして、恒久的な指定は、取り消しにくいこと。恒久転送はブラウザに記憶されるので、あとから変えても届かない相手が出ます。迷いがあるうちは一時的にしておく判断があります。

    最後に、仕組みを変えたら、ブラウザ任せだった部分を確認すること。画面を読み込み直さない作りにした時点で、いくつかの仕事がこちら側に移っています。移ったことに気づかないまま「昔から動くもの」として扱うと、静かに動かなくなります。