数ヵ月前、私たちはウェブサイトのNuxtのバージョンを2から3へアップグレードしました。この作業は、本来それほど大きな影響を与えるものではなく、比較的単純な作業のはずでした。ところが、このアップデートが、まさか数ヵ月にもわたって解決できない問題の始まりになるとは、私たち自身も予想していなかったのです。
私たちに何が起ったのか。
Nuxt を 3.15.4 にアップグレードしてからわずか1週間後、Google Search Console(GSC)で確認できるインデックス数が、突然半分以下にまで激減しました。およそ400ページあったものが、200ページ未満にまで落ち込んでしまったのです。本来こんなことは起きるはずがありません。
最初に疑ったのは、GSCに関連するサイトマップや robots.txt の設定でした。「サイトマップに未インデックスのページが含まれていないのでは?」と考えて確認しましたが、特に問題はありません。次に、「robots.txt で誤ってページをブロックしているのでは?」とも疑いましたが、こちらも異常は見つかりませんでした。
その後もいくつかの調整を試みましたが、どれも効果はなく、状況は改善しませんでした。そしてようやく私たちは気づくことになります――この問題は、想像以上に時間と労力を要する、厄介なものなのだと。「これは長期戦になる」と、腹をくくるしかありませんでした。
エンジニア
B.S.
Googleにウェブページが正しくインデックスされないケースにはいくつか考えられる理由があります。
・ページの表示エラー
・重複するコンテンツ
・文字数が極端に少ない、または低品質なコンテンツ
こうした理由は、よくあるものであり、基本的には比較的簡単に修正することが可能です。
たとえば、サーバーエラーが原因でページが表示できない場合は、その原因を特定して修正すれば、ページは正常に読み込まれ、再びインデックスされるようになります。コンテンツの重複が問題であれば、内容を編集して独自性を持たせたり、canonicalタグを追加して「このページが正規のものなので、このURLをインデックスしてほしい」とGoogleに伝えることで、問題を解消できます。また、コンテンツが短すぎたり、ページの品質が低いと判断されている場合も、内容を改善することでインデックスされる可能性が高まります。
しかし、GSC(Google Search Console)でのインデックス問題を一つひとつ潰していく中で、私たちを混乱させ、ほとんど解決不可能に思えるものがありました。それが「ソフト404」というエラーです。
GSCにおけるソフト404とは、「実際には“404 Not Found”を返していないにもかかわらず、何らかの理由で正しくページが読み込まれていないとGoogleが判断している状態」を指します。とはいえ、「具体的に何が原因なのか」について、GSCは教えてくれません。そのため、原因を突き止めるには、自分たちで地道に検証していくしかないのが現状です。
Nuxt 3 へのアップデートから約1週間が経過した頃、チームのメンバーのひとりが「今回の GSC の問題や、Google検索結果でのページ表示の不具合は、Nuxt 3 へのアップデートそのものが原因ではないか」と気づきました。
さらに、別の問題も発覚しました。なんと一部のページが、検索結果上にエラーメッセージ付きで表示されていたのです。意外に思われるかもしれませんが、Google は時として、明らかにエラーを返しているページであってもインデックスしてしまうことがあります。なぜそのようなことが起きるのかは、私たちにも理解が及びません……。
たとえば、トップページを読み込もうとした際に、サーバー処理の失敗によって「500 Server Error」が表示されるケースを想像してみてください。Google はそれを不具合とは認識せず、その状態のままインデックスしてしまうのです。その結果、ユーザーが私たちのサイトを検索した際に、検索結果のタイトルには「500 Server Error」と表示され、説明文にもサーバーエラーの詳細がそのまま載ってしまう、という事態が起こっていました。しかも、そのエラーが発生していたのは、よりによってサイトで最も重要なトップページだったのです。
当然、私たちはこのトップページの問題を最優先で解決する必要がありました。なぜなら、サイト全体の印象や SEO に甚大な悪影響を及ぼす可能性があったからです。検索結果のタイトルにエラーが表示されているサイトを、誰も好んでクリックしたいとは思わないでしょう。そこで、私たちはこの問題の修正に着手しつつ、並行してフロントエンドの改善作業も進めていきました。
現状考え得る限りの方法を試して、検索結果に正しくトップページを表示させようとしましたが、どれも効果はありませんでした。数週間、試行錯誤を続けた後、「このままでは無理だ」と感じ、Nuxt 2 にバージョンをダウングレードすることも検討しました。しかし、Nuxt 2に戻してしまうと、直近で行っていたフロントエンドの改善がすべて失われてしまうことになります。開発者としては、「状況を投げ出す」のではなく、解決に挑む覚悟を持っていたため、ロールバックは現実的な選択肢ではありませんでした。
私たちは、数日でも数週間でもなく、なんと数ヵ月にもわたって調査を続け、何十通りもの方法を試しながら、問題の解決を目指してきました。しかも、解決すべき「問題」は一つではありませんでした。ソフト404に加え、ホームページのメタタイトルにエラーメッセージが表示されるという、厄介な不具合まで発生していたのです。そんな中、Redditの投稿で、あるエンジニアのコメントを偶然見つけました。そこに書かれていた内容は、まさに私たちが直面していた問題と完全に一致していたのです。投稿者が提案していた解決策は、これまで私たちが一度も試したことのない方法でした。「もしかしたら、今回は運が味方してくれるかもしれない」――そう思い、試してみることにしたのです。
その Reddit に投稿したエンジニアによると、原因は Nuxt が生成する「チャンク(chunk)」にあるとのことでした。チャンクとは、JavaScript をいくつかの小さなファイルに分割したもので、「7vEvX3qN.js」といったファイル名で「_nuxt」フォルダに保存されています。これらは、ページ毎に必要なコードだけを読み込む仕組みで、サイトを高速化するために非常に重要な役割を果たしています。
もしチャンクがなければ、各ページはサイト全体で必要なコードが含まれた単一のJavaScript ファイルを毎回読み込む必要があります。例えば、トップページを開くだけなのに「お問い合わせ」ページにしか使わないコードまで読み込むことになり、結果としてページの表示にとても時間がかかってしまいます。つまり、この「チャンク分割」という機能は必要不可欠なものであるように思えます。しかし、実際のところはどうかというと、そうでもなかったのです。
例のRedditの投稿者によると、問題はGooglebot がページをクロールするときに起こるのだそうです。Googlebot はページ内のファイルを次々とダウンロードしていくのですが、30個以上のチャンクファイル(私たちのウェブサイトもそうでした)を扱うことができず、途中でダウンロードをやめてしまうのだそうです。その結果、一部のチャンクが無視されて読み込まれず、ページ側が存在しないファイルを呼び出そうとしてエラーが発生――それが、私たちのコーポレートサイトで起きていた現象だったのです。
ここで複雑なのは、このエラーが「Googlebotがクロールしたときのみ」発生するということでした。私たちが直接ページを開くときは、問題なく表示されます。
さらに混乱を招いたのが GSC(Google Search Console)の「公開URLテスト(ライブテスト)」機能です。このボタンを押すと、ページのインデックス状況や各種情報、さらにはスクリーンショットまで確認できます。しかし何度試しても、ページは正常に読み込まれていると表示されました。つまり、私たちが見る限りでは問題がありません。しかし実際には Googlebot がクロールした際に飲みエラーが発生し、そのエラー情報が検索結果に反映されてしまっていたのです。
Redditの投稿で紹介されていた解決方法は、非常にシンプルなものでした。それは、Nuxtの設定で「すべてのJavaScriptチャンクを一つの大きなファイルにまとめる」機能を有効にする、という内容です。この方法であれば、Googlebot は複数のファイルを個別に読み込む必要がなく、ひとつのファイルだけを読み込めば済むため、読み込みエラーが発生しにくくなる、という発想でした。
ただし、この方法には副作用がありました。エンジニアの方はもしかすると想像できるかもしれませんが、大きなJavaScriptファイルをページ表示のたびに読み込むため、サイトの表示速度が著しく落ちてしまうのです。では問題は解決したのか? ― 技術的には解決しました。けれども…。
実際にnuxt.config.tsに以下の短いコードを追加して公開したところ、数日、いや1週間も経たないうちに、検索結果でトップページにエラーは表示されなくなりました。代わりに、社名などを含んだ正しいメタタイトルが表示されるようになったのです。こうしてGooglebotがクロールしても正常にページが読み込まれるようになりました。
build: {
rollupOptions: {
output: {
inlineDynamicImports: true
}
}
}
これがnuxt.config.tsに追加したコードです。
トップページが正しく表示されるようになっただけでなく、これまでソフト404と認識されていた多くのページも、GSC上でインデックスされ始めました。もしこれで終わりであれば、「問題は解決した」と言えたかもしれません。しかし、この新たな「解決策」は、別の問題を引き起こしてしまったのです。というのも、すべてのJavaScriptチャンクを一つにまとめた結果、サイト全体のパフォーマンスがこれまでで最悪の状態になってしまいました。どのページを開いても、完全に読み込まれるまでに数秒かかるようになってしまったのです。そのため、当初は有効な解決策に見えたものの、最終的には「ウェブサイトのパフォーマンスを損なうことなく問題を解決する、別の方法を模索する」という方向に舵を切ることになりました。
私たちのチームはなんとかページを再びインデックスさせることに成功し、コーポレートサイトもようやく検索結果で正しく表示されるようになりました。確かにサイトのパフォーマンスは相当悪化していましたが、それでも以前までの問題に比べれば良い方でした。そこで、もっと良い解決策が見つかるまで「チャンクをまとめてひとつのファイルにする方法」でしばらく暫定的に運用することにしました。
それから約1ヵ月間、GSCの問題を解決する別の方法を模索し、試行錯誤を重ねていたところ、偶然にも新しい方法を発見しました。それは、誰も思いつかなかった手法であり、やはりJavaScriptのチャンクが関係していたのです。
新しい解決策は、サイトの「_nuxt」フォルダに関するものでした。ご想像の通り、このフォルダにはチャンクファイルが保存されています。最初は、「Googlebot がページをクロールして、ページがチャンクファイルを呼び出し、それが見つからないからエラーになる」と思っていました。
つまり、問題の原因は「ページ側」にあると考えていたのです。でももしそうなら、なぜ Googlebot がクロールしたとき以外は正常にページが表示されるのでしょう?――この新しい解決策のおかげで、ようやく問題の本当の原因に気づくことができました。
実際の問題は「ページがチャンクファイルを読み込むこと」ではありませんでした。問題は「Googlebot までがチャンクファイルを読み込もうとしていたこと」だったのです。本来、Googlebot は「_nuxt」フォルダをクロールするべきではないと、ある記事(今回は Reddit ではなく別のサイト)に書かれていました。では、どうやってそれを防ぐのか?方法はシンプルでした。
robots.txt ファイルに以下のルールを追加するだけです。
Disallow: /_nuxt/
この設定を追加することで、Googlebot は「_nuxt」フォルダを無視するようになりました。その結果、チャンクファイルを読み込もうとしてエラーが発生することもなくなったのです。
新しい解決策は、かつて導入していた、サイトのパフォーマンスを悪化させていた方法に代わるものでした。そこで、私たちは再びチャンク機能を有効にすることにしました。具体的には、以前追加していた inlineDynamicImports: true のコードを削除し、JavaScriptを30個以上のファイルに分割する設定に戻したのです。そして、その状態で数日間にわたり、サイトの動作を観察しました。
当初は「チャンクを再び有効化すれば、ソフト404の問題や、検索結果にホームページがエラーメッセージ付きで表示される不具合が再発するのではないか」と心配していました。しかし、ありがたいことに、そのような問題は再発しませんでした。
robots.txt に「_nuxt」フォルダをクロールしない設定を追加したうえでチャンクを元の状態に戻したところ、インデックスされるページ数にも、検索結果での表示にも、まったく問題は発生しませんでした。
ソフト404の問題や、ホームページが検索結果のメタ情報にエラーとして表示される問題を解決してから、すでに2ヵ月ほどが経過しました。現在までのところ再発の兆しは一切なく、安定した状態が続いています。
もちろん、GSCにはまだいくつかの問題が残っています。しかし、それらについては内容を正しく理解できており、対処方法も把握しているため、時間をかけて一つずつ対応している状況です。
今回のソフト404やホームページのエラー表示が特に厄介だったのは、とにかく分かりづらく、予想外の原因による問題だったことです。フロントエンド開発に10年以上携わってきた私たちでも、すぐに原因を特定することはできませんでした。最終的にサイトを正常な状態に戻すまでには、およそ4ヵ月にわたる調査と試行錯誤が必要でした。
もし、あなたが今、同じような問題に直面しているのであれば、この記事が少しでも解決の手助けになればと願っています。私たち自身も、Redditやその他のサイトで経験を共有してくれた方々の助けがなければ、ここまでたどり着くことはできなかったでしょう。だからこそ、この経験を通じて、同じようなエラーに悩む方々やエンジニアコミュニティに、少しでも恩返しができれば嬉しく思います。
今日もあなたに気づきと発見がありますように