はじめに
個人開発でWebツール集「猫の手道具箱」をReactで開発・運用しているのですが、皆さんはこんな経験はありませんか?
SEO対策のためにsitemap.xmlをpublicフォルダに設置したのに、なぜか表示されたりされなかったり…。僕自身、このサイトで「ブラウザから直接アクセスすると見えるのに、サイト内を回遊してからだと表示されない」という現象に直面し、頭を抱えました。
この記事では、多くの開発者が一度はハマるこの問題について、
- なぜ「時々」表示されなくなるのか?という根本原因
- Webサーバー側での確実な解決策
を、実際の挙動に沿って分かりやすく解説します。
現象の再現:なぜ挙動が変わるのか? 🤔
まず、多くの人が混乱する具体的な現象を見てみましょう。
-
パターンA:成功するケース ✅
- ブラウザのシークレットモードを開き、https://example.com/sitemap.xmlに**直接**アクセスする。
- 結果: XMLファイルが問題なく表示される。
-
パターンB:失敗するケース ❌
- まずサイトのトップページ https://example.com/ を表示する。
- アプリ内の<Link>などでページをいくつか遷移する。
- その後、アドレスバーに手動で https://example.com/sitemap.xml と入力し、Enterキーを押す。
- 結果: なぜかアプリのトップページやNot Foundページが表示されてしまう。
ファイルはサーバー上に確かにあるはずなのに、なぜ閲覧の仕方によって結果が変わるのでしょうか?
この挙動の違いこそが、React Routerを使ったSPA特有の「罠」なのです。
根本原因は「ルーティングの主導権」の交代劇
この問題の核心は、URLを処理する主役が誰なのかが途中で入れ替わる点にあります。
パターンA:Webサーバーが主役のとき
シークレットウィンドウから直接 /sitemap.xml にアクセスした場合、ブラウザはWebサーバーに「/sitemap.xmlというファイルをください」と素直にリクエストを送ります。
サーバーはリクエストを受け取り、公開ディレクトリから該当ファイルを探して返すだけ。これは昔ながらの静的なWebサイトの動きであり、主役は完全にWebサーバーです。React Routerはまだ登場すらしていません。
パターンB:React Routerが主役のとき
一度でもサイト(例: /)にアクセスすると、Webサーバーはindex.htmlを返します。このindex.htmlがブラウザに読み込まれた瞬間、主役が交代します。
index.html内のJavaScriptが実行され、React Routerが「これ以降のページ遷移は私が担当します!」と、ブラウザのルーティング機能を乗っ取ります(専門的にはHistory APIを制御します)。
この状態でアドレスバーのURLが/sitemap.xmlに変わると…
- React RouterがURLの変更を検知し、「サーバーにリクエストを送るな!」と横取りします。
- 自身のルーティング設定(<Routes>やcreateBrowserRouterの中身)を見て、「/sitemap.xmlに一致するルートはあるかな?」と探します。
- 通常は定義されていないため、「一致するルートが見つからない。じゃあNot Foundページを表示しよう」と判断します。
これが、ファイルがそこにあるにも関わらず、アプリのページが表示されてしまう現象の正体です。ブラウザはサーバーにsitemap.xmlを問い合わせることすらしていないのです。
解決策:サーバーに「静的ファイルを優先して!」と教える
この問題を解決するには、「/sitemap.xmlのような特定のファイルへのアクセスが来たら、React Routerは口出しせず、Webサーバーに処理を任せる」というルールを作る必要があります。
最も確実な方法:サーバー設定
Webサーバーの設定ファイルに、静的ファイルを優先的に配信するルールを明記するのが最も確実で推奨される方法です。
Nginx の設定例 (nginx.conf)
server {
\# ... 中略 ...
root /var/www/html; \# ビルドしたファイルが置かれているディレクトリ
\# sitemap.xmlやrobots.txtへのリクエストが来たら、
\# Webサーバーが直接ファイルを返すようにする
location \~ ^/(sitemap\\.xml|robots\\.txt)$ {
try\_files $uri \=404;
}
\# 上記のルールに当てはまらないリクエストは、
\# すべてReactアプリ(index.html)にフォールバックさせる
location / {
try\_files $uri $uri/ /index.html;
}
}
Apache の設定例 (.htaccess)
ビルドしたファイルと同じ階層に.htaccessファイルを設置します。
RewriteEngine On
\# リクエストされたパスが実在するファイルやディレクトリでなければ、
\# すべてindex.htmlにリダイレクトする
RewriteCond %{REQUEST\_FILENAME} \!-f
RewriteCond %{REQUEST\_FILENAME} \!-d
RewriteRule . /index.html \[L\]
この設定は「リクエストされたパスにファイルが存在しない場合にindex.htmlを返す」というルールです。/sitemap.xmlは実在するファイルなので、このルールの対象外となり、サーバーが直接ファイルを返してくれます。
でも、一度アプリを表示した後はやっぱり表示されない…諦めていいの?
鋭い方はお気づきかもしれませんが、このサーバー設定を行っても、パターンB(アプリ表示後にURLを手入力)の現象は解決しません。なぜなら、一度React Routerが主導権を握ってしまうと、ブラウザはサーバーに問い合わせる前にURLの変更をクライアントサイドで処理してしまうからです。
じゃあ、これは諦めるしかないのでしょうか?
結論から言うと、はい、ほとんどの場合は諦めて問題ありません。
私たちが本当に解決したかった問題は以下の2つです。
- Googleなどの検索クローラーがsitemap.xmlを確実に読み取れること。
- ユーザーが直接URLにアクセスした際にファイルが表示されること。
サーバー設定は、この2つの重要な目的を完璧に達成してくれます。クローラーや直接リンクからのアクセスは、必ずサーバーが処理するためです。
「アプリを回遊中のユーザーが、急にアドレスバーにsitemap.xmlと手入力する」というシナリオは非常に稀であり、SEOや主要なユーザー体験には影響しません。
SPAの快適なナビゲーションをユーザーに提供しつつ、クローラーには静的ファイルをきちんと見せる。この両立こそが重要であり、サーバー設定はそのためのベストな解決策なのです。