Next.jsの静的エクスポートで作ったこのサイトで、存在しないURLにアクセスすると404ページは表示されるのに、HTTPステータスが200になっていることに気づきました。いわゆる「ソフト404」の状態です。原因と直し方をまとめます。
確認方法
curl でステータスコードだけを表示すると簡単に確認できます。
$ curl -s -o /dev/null -w '%{http_code}\n' https://example.com/not-exist-page/
200
見た目は「ページが見つかりません」なのに、ステータスは200です。検索エンジンからは「中身の薄い通常のページ」と扱われることがあります。
原因
.htaccess が次のようになっていました。
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /404.html [L]
ErrorDocument 404 /404.html
存在しないファイルへのリクエストを RewriteRule で /404.html に内部的に書き換えているため、Apacheからすると「404.html というファイルを正常に返した」ことになり、ステータスは200になります。ErrorDocument も書いてありますが、その前に書き換えられてしまうので使われていませんでした。
対処
RewriteRule での書き換えをやめ、ErrorDocument だけにしました。
# 存在しないページは 404 ステータスで 404.html を返す
ErrorDocument 404 /404.html
<IfModule mod_rewrite.c>
RewriteEngine On
# HTTPS強制
RewriteCond %{HTTPS} !=on [NC]
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</IfModule>
$ curl -s -o /dev/null -w '%{http_code}\n' https://example.com/not-exist-page/
404
表示される内容は同じ404ページのまま、ステータスだけ404になりました。Next.jsの静的エクスポートでは out/404.html が出力されるので、それをそのまま使えます。
補足:確認時はキャッシュに注意
Xserverのサーバーキャッシュが有効な場合、.htaccess を変更した直後でも、キャッシュされた古いレスポンスが返ることがありました。確認するときはクエリ文字列を付けると、キャッシュを避けて確認できます。
$ curl -s -o /dev/null -w '%{http_code}\n' "https://example.com/not-exist-page/?v=$RANDOM"
また、サブディレクトリに別のサイト(サブドメインなど)を置いている場合、ErrorDocument などの設定は親の .htaccess から継承されます。子側で ErrorDocument 404 default を書いて既定に戻すなど、影響が出ないか確認しておくと安心です。