Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】My German links unfurl in English, and I decided that's OK (for now)

2026年10月14日に「Dev.to」が公開したITニュース「My German links unfurl in English, and I decided that's OK (for now)」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

アプリ開発者が多言語対応アプリの課題を共有する。言語をクエリパラメータで実装した結果、SNSプレビューが英語になる問題に直面。SEOのためのプリレンダリングで生じたバグは、タイミングではなくスナップショットの内容確認で解決した。多言語は最初からパスで実装すべきだったと教訓を得ている。

ITニュース解説

ある開発者が自身の開発したAIヘッドショットアプリの多言語対応について、どのように実装し、どのような課題に直面し、最終的にどう判断したかを語っている。このアプリはReactとTypeScriptで開発され、Firebase Hostingで公開されており、Next.jsは使っていない。英語、スペイン語、フランス語、ドイツ語、中国語の5言語に対応している。

筆者が直面した問題は、ドイツ語のリンクをSlackに貼り付けた際、そのプレビュー表示が英語になってしまうことだった。これは、ウェブサイトの多言語対応の方法と、事前レンダリングと呼ばれる処理、そして外部サービスとの連携の複雑さに起因する。

まず、アプリの多言語対応の仕組みから説明する。このアプリでは、ウェブサイトの言語をURLの「クエリパラメータ」という部分で指定している。例えば、「?lang=de」のように、URLの末尾に?lang=deとつけることでドイツ語のページを表示する。このような仕組みにしたのは、開発初期にウェブサイトのルーティング(URLと表示する内容を結びつける仕組み)を大きく変更する手間を省きたかったためだ。

言語の切り替えには「i18next」というライブラリが使われており、これがクエリパラメータやブラウザの設定などから最適な言語を判断する。また、各ページにはhreflangという特別なHTMLタグが埋め込まれている。これは、同じ内容のページが複数の言語で存在する場合に、検索エンジン(Googleなど)に「このページにはドイツ語版、フランス語版などがあるよ」と教えるための重要な情報だ。これにより、検索結果でユーザーの言語に合ったページが表示されやすくなる。

次に、このアプリがどのようにして検索エンジンに内容を伝えているかを見てみよう。一般的な「シングルページアプリケーション(SPA)」と呼ばれるウェブサイトは、最初にブラウザに送られるHTMLファイルがほとんど空っぽで、JavaScriptが実行されてから初めてコンテンツが表示される。これだと、Googleなどの検索エンジンがウェブサイトを巡回する際に使う「クローラー」というプログラムが、正しい内容を読み取れない可能性がある。そこで筆者は、「事前レンダリング(Prerendering)」という方法を採用した。これは、「サーバーサイドレンダリング(SSR)」と似ているが、サーバーが毎回動的にHTMLを生成するのではなく、事前に各ページのHTMLファイルを生成して保存しておく方式だ。

具体的には、ウェブサイトをビルドした後、専用のスクリプトが「Puppeteer」というツール(ヘッドレスブラウザ、つまり画面表示のないChromeブラウザのようなもの)を使って、各ページを実際に開く。そして、ページが完全に表示されるまで待ってから、その時点のHTMLの内容を静的なファイルとして保存する。これにより、クローラーは最初から内容が記述されたHTMLファイルを読み込めるため、SEO(検索エンジン最適化)に有利となる。

しかし、この事前レンダリングのプロセスでいくつかの予期せぬバグが発生した。

一つ目のバグは、「二重のmetaタグ」問題だ。metaタグは、ウェブサイトのタイトルや説明などを検索エンジンに伝える重要な情報だが、初期のHTMLファイルには汎用的なmetaタグが記述されており、さらにJavaScriptが実行されてから、各ページ固有のmetaタグが追加されていた。これにより、同じ種類のmetaタグが二つ存在することになり、一部のクローラーは最初の情報だけを読み取ってしまい、正しいページ情報が伝わらない可能性があった。この問題は、事前レンダリングのスクリプトがHTMLファイルを生成した後、重複するmetaタグを削除する後処理を追加することで解決した。

二つ目のバグは、「正しいページなのに間違ったタイトルが表示される」という問題だ。このアプリは、記事の内容を「Firestore」というデータベースから取得して表示するが、データが完全に読み込まれる前に事前レンダリングの処理が進んでしまい、記事の内容は表示されていても、ウェブサイト全体の汎用的なタイトルがHTMLに書き込まれてしまうことがあった。これは、データの読み込み完了を待つタイミングの判断が難しかったためだ。最終的に筆者は、ページが完全にレンダリングされ、かつ記事固有のタイトルが正しく反映されているかどうかを、生成されたHTMLの内容自体をチェックする仕組みを導入することで解決した。つまり、特定のルート(URL)で生成されたHTMLに、もし汎用的なタイトルが含まれていれば、その事前レンダリングは失敗とみなし、以前の正しいファイルを維持するという厳格なチェックを取り入れた。これにより、たとえ古い情報であっても、壊れた情報が公開されることを防いだ。

(補足として、アプリがGoogleの認証サービス「reCAPTCHA」を使っていた関係で、事前レンダリング中に記事データが取得できない問題も発生した。これは、事前レンダリングのスクリプトが、Firestoreの公開REST APIを使って記事データを直接取得し、ページが読み込まれる前にそのデータを挿入することで解決された。)

そして、冒頭で触れたSlackのプレビュー表示の問題に戻る。この問題の根本原因は、Firebase Hostingの「リライトルール」という仕組みが、URLのパス部分(例: /や/articles/)に基づいてファイルを割り当てるが、クエリパラメータ(?lang=deなど)は考慮しないことにある。そのため、/?lang=deや/?lang=esといった異なる言語のURLでも、Firebase Hostingは同じ事前レンダリング済みの英語のindex.htmlファイルを返してしまう。

GoogleのクローラーはJavaScriptを実行できるため、この英語のHTMLを受け取っても、その後JavaScriptを実行して正しい言語のコンテンツとhreflangタグを認識できる。しかし、SlackやiMessage、LinkedInなどのSNSは、URLのクエリパラメータを認識せず、最初に受け取った生のHTML(この場合は英語のindex.html)を基にプレビュー情報(og:titleなどのOpen Graphタグ)を生成してしまうため、結果として英語のタイトルが表示されてしまうのだ。

この問題を完全に解決するには、言語ごとに異なる静的HTMLファイルを生成し、さらにURLのクエリパラメータを考慮して正しい言語のHTMLファイルを返すような、ウェブサイトのビルドプロセスとインフラストラクチャを大幅に変更する必要がある。これは簡単な修正では済まないため、筆者は「今のところはこれで良い」と判断し、非英語圏のユーザーからの共有が増え、実際に問題になるまでは対応しないという選択をした。

もし最初からこのアプリを開発し直すとしたら、筆者は言語指定をクエリパラメータではなく、URLのパスに含める方式(例: /de/や/fr/)を採用すると述べている。このパスベースの方式であれば、Firebase Hostingのリライトルールも適用しやすく、最初の手間はかかったとしても、後々の問題が少なかっただろうという反省がある。また、事前レンダリングの品質チェックは、タイミングに依存する推測的な方法ではなく、常に最終的なHTMLコンテンツを検証する、より堅牢な方法を最初から導入すべきだったとも語っている。

この筆者の経験は、システムの設計段階での小さな選択が、後に大きな問題や追加のコストに繋がること、そして複雑な問題を解決するためには、安易なタイミングでの判断ではなく、結果に基づいた厳密な検証が重要であることを教えてくれる。

関連コンテンツ

関連IT用語

関連ITニュース