この記事で扱うこと
Web ページは 2 種類の読者 を同時に相手にする。一つは人間(画面を見てスクロールする訪問者)、もう一つは機械(検索クローラー、リンクをプレビューに展開するボット、分析ツール)だ。今日は、この二つが 同じ事実を見られる ようにしつつ、コードが散らからないよう整理する作業をした。
具体的には四つ。① ログインしていない訪問者・検索ボットにもホームが読む材料を持てるよう開き、② ログイン情報をまだ確認中のときに公開ページが壊れないよう防ぎ、③ 分析イベントを複数のツールへ一度に送る通路を作り、④ ページ遷移のアニメがアクセシビリティ・スクロールと衝突しないよう止めた。
特定の製品・会社の話はせず、その日に出会った 汎用 Web 概念 だけを整理する。
一日のまとめ
| 作業 | 何をしたかったか | やったこと | 結果 |
|---|---|---|---|
| 公開ホーム・SEO | 検索ボット・訪問者がホームで読む内容を持てるように | 画面の文とボット用の隠し情報を 一つのデータ から取り出す | 中身の薄いページ・偽情報を防止 |
| ログイン確認待ち | ログイン確認中に公開ページが壊れないように | 「確認が終わり + 実際にログイン」のときだけ保護 API を呼ぶ | 未ログイン・検索経路が安定 |
| 分析の通路 | イベントを複数の分析ツールへ漏れなく送りたい | 画面は通路一つだけ呼び、裏で複数ツールへコピーする | ツールごとに呼ぶミスを排除 |
| ページ遷移 | なめらかな遷移 + アクセシビリティ/スクロール保持 | 「アニメを減らす」設定なら遷移そのものをオフに | スクロール衝突・テスト崩れを予防 |
1. 画面にない情報をボット用マークアップにだけこっそり入れない
検索エンジンは、人が見る画面の文だけでなく、ページに隠した 機械用の要約情報 も読む。この隠し情報と画面の文が食い違うと「ごまかし」と見なされ損をする。今日学んだのは この二つを常に同じに保つ方法 だ。
前提知識
- 構造化データ(structured data) — ① 検索エンジンがページ内容を素早く理解できるよう入れる、人の目には見えない要約情報。② ないと検索結果に星評価・FAQ のような豊かな表示が出ない。③ 多くは JSON-LD(この要約を JSON 形式で書いたもの)というスクリプトで入れる。
- 可視コンテンツ(visible content) — つまり、人が実際に画面で目にする文。
- thin content(中身の薄いページ) — つまり、読む中身がほとんど無く検索エンジンに低品質と判断されるページ。ログイン壁で本文が空になるとこうなる。
- sitemap(サイトマップ) — つまり、「うちのサイトにはこんな住所がありますよ」と検索ボットに渡す住所リスト。
どんな状況だったか。 ログインが要るページが多く、未ログイン・検索ボットの目にはホームが空に見えやすかった。逆に画面には無いのに FAQ 要約情報だけ大量に入れると「ごまかし」になる。
何を学んだか。 隠した要約情報は 画面の文と必ず一致 させる。FAQ リストを一つのデータにして画面と要約が 一緒に それを使えば、画面から FAQ を外した瞬間に要約からも自動で外れる。ログイン壁をぼかし(ブラー)処理しても、その上に本物のタイトル・説明・FAQ の文を載せて薄いページを避ける。サイトマップには 実際に開ける(正常応答を返す)内部住所だけ を入れ、外部へ飛んでいくページは無理に入れない。
2. ログイン確認が終わる前に保護された API を呼ばない
ログインが要るデータを取るとき、「ログイン情報をまだ確認中の状態」と「確認したら未ログインだった状態」を取り違えると、公開ページが誤ってログイン画面へ弾かれる。この二つを見分ける方法を学んだ。
前提知識
- 保護(された)API — つまり、ログインした人だけが呼べるサーバー住所。ログイン情報(Cookie)なしで呼ぶとサーバーは 401(権限なし)エラーを返す。
- セッションローディング — つまり、ブラウザが「この人はログイン済みか?」をサーバーに尋ねて答えを待つ数瞬間。この間はログイン有無がまだ 不明。
enabled— データ取得ライブラリ(例: React Query)で「この条件が真のときだけ要求を送れ」とオン・オフするスイッチ。つまりenabledが偽なら API を一切呼ばない。- モック(mock)サーバー — つまり、本物の代わりに前もって決めた偽の応答を返すテスト用サーバー。
どんな状況だったか。 Cookie のない訪問で保護 API を呼んで 401 が出て、「401 ならログインへ送る」というグローバル規則が働き、公開ホームまで ログイン画面へ弾かれた。テストは偽サーバーが常に正常応答を返すため、この問題を捕まえられなかった。
何を学んだか。 「ユーザー情報が無い(user === null)」は 未ログインではなく、まだ確認されていない かもしれない。だから要求スイッチは authResolved && isAuthenticated(ログイン確認が 終わり + 実際にログインしたユーザーのとき)のように 二つの条件を一緒に 見る必要がある。公開ページは保護 API を切り、静的・偽データだけを使う。検証は Cookie を消した実ブラウザで行う。
3. 分析イベントは通路一つにまとめて複数の場所へ送る
「ユーザーがボタンを押した」のような記録を複数の分析ツールへ送るとき、画面コードが各ツールを直接呼ぶと、うっかり一つにしか送らなかったり、ツールのエラーが決済コードまで壊したりする。間に 通路一つ を置くパターンを学んだ。
前提知識
- 分析イベント(analytics event) — つまり、「登録完了」「決済ボタンのクリック」のようなユーザー行動の記録。これを集めて利用統計を出す。
- ベンダー SDK — つまり、分析会社(Google など)が用意した、イベントをその会社のサーバーへ送るコードの束。
- ファサード(facade)パターン — ① 後ろに複雑なものが複数あっても、前には 単純な窓口一つ だけを置く設計。② 画面コードがツールの種類を知らなくてよくなる。③ 銀行の複数部署の代わりに窓口係一人とだけ話すようなもの。
- ファンアウト(fan-out) — つまり、入ってきた一つを複数の宛先へ勝手にコピーして送ること。
どんな状況だったか。 画面ごとに分析ツールのコードを直接呼ぶと、ある画面は一つのツールにしか記録を送らず統計がずれ、ツールがエラーを出すとその例外が決済・ログインの流れまで揺らしかねなかった。
何を学んだか。 画面は track(イベント記録)・identify(誰かを知らせる)・reset(ログアウト時に初期化)という 窓口関数三つだけ を知る。その裏で実際の複数の分析ツールへ コピーして撒く。各ツールは設定で個別にオン・オフし、ツール呼び出しが失敗しても静かに握りつぶして 本来の機能(決済など)は止めない。どこでも使われていないのに export だけされたヘルパーは、移動・整理のときに蘇らせない。
4. 「アニメを減らす」設定ならページ遷移をアプリ側でオフにする
ページを移るとき、なめらかに重なって消える効果(cross-fade)を入れたかった。だがめまい等で アニメをオフにしてと設定したユーザー には、この効果はむしろ害になる。CSS で時間を 0 に縮めるだけでは足りず、遷移機能そのものをオフにする必要があった。
前提知識
prefers-reduced-motion— ① ユーザーが OS/ブラウザで「アニメを最小化」をオンにしているか教える設定値。② めまい・乗り物酔いに敏感な人のためのアクセシビリティ機能。③ オンなら派手な動きを外すべき。- JS スプリング遷移 vs CSS 遷移 — つまり、アニメを JavaScript が計算して動かす(スプリング)と、CSS で時間(
--duration)を 0 に縮めても JS 側は生きたまま で止まらない。 matchMedia+useSyncExternalStore— つまり、上の設定値が変わるのをリアルタイムに検知(matchMedia)し、React 画面へ安全に反映(useSyncExternalStore)する方法。- Provider(プロバイダ) — つまり、ある機能を配下の画面全体へ供給するラッパーコンポーネント。ここでは「ページ遷移機能」をオンにするスイッチ役。
- 仮想リスト(virtual list) — つまり、長いリストで画面に見える数個だけを実際に描いて性能を節約する手法。スクロール位置の管理が繊細。
どんな状況だったか。 グローバルな遷移効果を入れると、アニメは JavaScript が動かすので CSS の時間だけ 0 にしても止まらず、長いリストのスクロール位置や画面比較テスト(VR)と衝突する恐れがあった。
何を学んだか。 「アニメを最小化」設定をリアルタイムに購読し、オンなら遷移機能の供給者(Provider)を そもそもオンにしない(マウントしない)。そして遷移効果は 見た目だけ を担い、スクロール位置には触らない ことで、リストが自分でスクロールを復元する権限と衝突しないよう分離する。
→ prefers-reduced-motion と JS ページ遷移ゲート