連載「Cloudflare Pages で公開する」第2回

Cloudflare Pages で存在しない URL が 200 になる(ソフト404)原因と対処|Astro の 404 ページと独自ドメイン設定

この回でやること

Cloudflare Pages に置いた Astro のサイトで、次の2つを行います。連載「Cloudflare Pages で公開する」の第2回です。

  • 存在しない URL にトップページが 200 で返る状態を直し、正しく 404 を返す
  • 独自ドメイン(cyber-blow.com)を設定し、https へのリダイレクトと canonical を確かめる

連載の全体は次のとおりです。

前提として、Astro は静的出力(アダプターなし)で、Cloudflare Pages へのデプロイまでは済んでいるものとします。デプロイの手順は第1回に書いています。

存在しない URL にトップページが 200 で返ってくる

症状

デプロイしたあと、存在しない URL を開いても、エラーにならずにトップページが表示されました。ステータスコードも 404 ではなく 200 です。

原因:404.html が無いと SPA とみなされる

Cloudflare Pages は、出力の直下に 404.html が無いと、そのサイトを SPA(シングルページアプリケーション)だとみなします。そして、存在しないパスにはトップページ(/)を返します。

If your project does not include a top-level 404.html file, Pages assumes that you are deploying a single-page application. This includes frameworks like React, Vue, and Angular. Pages' default single-page application behavior serves the root (/) for any incoming path that does not correspond to a static asset, allowing your application to handle routes like /about or /help client-side from within your SPA.

出典:Serving Pages | Cloudflare Pages docs

SPA なら、どの URL でもトップの HTML を返し、ブラウザ側の JavaScript で画面を切り替えるので、この動きが便利です。しかし、ページごとに HTML を生成する Astro の静的サイトでは、存在しない URL にもトップページが返るだけになってしまいます。

なぜ困るのか:ソフト404

ページが存在しないのに 200 を返すと、検索エンジンからは、どの URL にもトップと同じ内容のページがあるように見えます。このように「存在しないページなのに 200 を返す」状態は、ソフト404 と呼ばれます。

Google のドキュメントでは、200 が返ってきても、中身がエラーや空のページを示していると判断した場合は、Search Console にソフト404 エラーとして表示すると説明されています。

参考:How HTTP status codes, and network and DNS errors affect Google Search | Google Search Central

対処:Astro で 404 ページを作る

Astro では、src/pages/404.astro を作ると、ビルドで 404.html が出力されます。

For a custom 404 error page, you can create a 404.astro or 404.md file in src/pages. This will build to a 404.html page. Most deploy services will find and use it.

出典:Pages | Astro Docs

このサイトの 404 ページは次のとおりです。共通レイアウトを使い、トップページとヘッダーのナビと同じリンクを並べて、迷った人が移動できるようにしています。

src/pages/404.astro
---
// 404.html があると、Cloudflare Pages は存在しないURLに 404 を返す
// (無いとトップページを 200 で返してしまい、検索エンジンにソフト404と見なされる)
import { NAV_ITEMS } from '../consts';
import BaseLayout from '../layouts/BaseLayout.astro';
---

<BaseLayout title="ページが見つかりません">
	<div class="not-found">
		<h1 class="page-title">ページが見つかりません</h1>
		<p>お探しのページは削除されたか、URLが変更された可能性があります。</p>
		<ul>
			<li><a href="/">トップページ</a></li>
			{
				NAV_ITEMS.map((item) => (
					<li>
						<a href={item.href}>{item.label}</a>
					</li>
				))
			}
		</ul>
	</div>
</BaseLayout>

確認する

デプロイしたあと、存在しない URL に curl でアクセスして、ステータスコードと中身を確かめました。

$ curl -s -o /dev/null -w '%{http_code}\n' https://cyber-blow.com/no-such-page/
404
$ curl -s https://cyber-blow.com/no-such-page/ | grep -o '<title>[^<]*</title>'
<title>ページが見つかりません | cyber-blow</title>

ステータスコードが 404 になり、404 ページの中身が返っています。

セクションごとに 404 ページを分けることもできる

Cloudflare Pages は、リクエストされたパスと同じディレクトリから順に上へたどって、いちばん近い 404.html を探します。たとえば /blog/404.html を置けば、/blog/ 以下だけブログ用の 404 ページにできます。

This means that you can define custom 404 paths for situations like /blog/404.html and /404.html, and Pages will automatically render the correct one depending on the situation.

出典:Serving Pages | Cloudflare Pages docs

このサイトは、いまのところ /404.html の1つだけです。

独自ドメイン cyber-blow.com を設定する

設定

Pages プロジェクトのカスタムドメインに cyber-blow.com を追加しました。

cyber-blow.com のような www などの付かないドメイン(apex ドメイン)を Pages で使うには、そのドメインが、Pages プロジェクトと同じ Cloudflare アカウントのゾーンになっている必要があります。

To deploy your Pages project to a custom apex domain, that custom domain must be a zone on the Cloudflare account you have created your Pages project on.

出典:Custom domains | Cloudflare Pages docs

確認したこと

設定したあと、次の3つを確かめました。

1つ目は、http でアクセスすると https に 301 でリダイレクトされることです。パスもそのまま引き継がれます。

$ curl -sI http://cyber-blow.com/ | grep -iE '^(HTTP|location)'
HTTP/1.1 301 Moved Permanently
Location: https://cyber-blow.com/
$ curl -sI http://cyber-blow.com/blog/ | grep -iE '^(HTTP|location)'
HTTP/1.1 301 Moved Permanently
Location: https://cyber-blow.com/blog/

2つ目は、canonical が https://cyber-blow.com/ から始まる URL になっていることです。

$ curl -s https://cyber-blow.com/ | grep -o '<link rel="canonical"[^>]*>'
<link rel="canonical" href="https://cyber-blow.com/">

3つ目は、Pages が最初に用意する <プロジェクト名>.pages.dev の URL でも、canonical が独自ドメインを指していることです。独自ドメインを設定したあとも、pages.dev の URL では同じ内容が見られます。canonical が独自ドメインを指していれば、検索エンジンにはこちらが正規の URL だと伝わります。

$ curl -s https://<PROJECT>.pages.dev/ | grep -o '<link rel="canonical"[^>]*>'
<link rel="canonical" href="https://cyber-blow.com/">

canonical はリクエストのホストではなく site 設定から作る

pages.dev でアクセスしても canonical が独自ドメインになるのは、canonical を astro.config.mjs の site から作っているからです。

astro.config.mjs
site: 'https://cyber-blow.com',
// canonical・サイトマップ・内部リンクのURL表記を「/」終わりに統一する
trailingSlash: 'always',
src/layouts/BaseLayout.astro
const canonical = new URL(Astro.url.pathname, Astro.site);

静的サイトなので、HTML はビルドのときに一度だけ作られます。どのホスト名でアクセスされても、canonical は site に書いた URL になります。

まだ残っていること

www.cyber-blow.com

www.cyber-blow.com はまだ設定していません。いまは名前解決できない状態です。

$ curl -sSI https://www.cyber-blow.com/
curl: (6) Could not resolve host: www.cyber-blow.com

設定するときに気をつけたいのは、Pages のダッシュボードでドメインを追加する前に、DNS に CNAME レコードだけを手で作ると、522 エラーになることです。

Manually adding a custom CNAME record pointing to your Cloudflare Pages site - without first associating the domain (or subdomains) in the Cloudflare Pages dashboard - will result in your domain failing to resolve at the CNAME record address, and display a 522 error.

出典:Custom domains | Cloudflare Pages docs

別の TLD から .com へのリダイレクト

cyber-blow.net・cyber-blow.site・cyber-blow.online から cyber-blow.com への 301 リダイレクトも、まだ設定していません。Cloudflare のリダイレクトルールで設定する予定です。

まとめ

  • Cloudflare Pages は、出力の直下に 404.html が無いと SPA とみなし、存在しない URL にトップページを 200 で返す
  • Astro では src/pages/404.astro を作れば 404.html が出力され、正しく 404 が返る
  • /blog/404.html のように、ディレクトリごとに 404 ページを分けることもできる
  • apex ドメインを Pages で使うには、そのドメインが同じ Cloudflare アカウントのゾーンである必要がある
  • 独自ドメインの設定後は、http→https の 301、canonical、pages.dev 側の canonical を curl で確かめる

次回は、microCMS で記事を公開したときに、Cloudflare Pages を自動で再ビルドする設定を扱います。