Nuxt Content v3 と Cloudflare Workers の trailing slash 問題
このサイトで記事詳細ページに直接アクセスすると、hydration mismatchが起きたり、title要素がundefinedになったりする問題が発生していた。
結構前からこの問題は認識していたのだが、対処法がわからず、そもそもアクセスが多いサイトでもないため放置してしまっていた。
結果的にCloudflare Workersの設定で解決できたのだが、過程で遠回りな対応もしていたため、以下に原因と対応をまとめる。
現象
記事詳細ページ(例えば今表示しているこのページ)へのアクセス方法によって、記事の取得結果が異なるという現象が発生していた。
- トップページから記事をクリックして遷移した場合、記事の内容やメタデータの取得が正常に行われる
- 直接記事詳細のURLにアクセスした場合、
queryCollection('blog').path(path).first()で取得する記事データがundefinedになり、hydration mismatchエラーが発生
原因
原因はCloudflare Workers Static Assetsのデフォルト設定とNuxt Contentのパス形式の不一致だった。
Nuxt Contentは記事を/blog/2025-10-08のようにtrailing slashなしで管理しているが、Cloudflare Workers Static Assetsにはhtml_handlingという設定があり、デフォルトはauto-trailing-slashになっている。
この設定では、ディレクトリ型のURL(/blog/2025-10-08/index.htmlのように生成されるもの)に対してtrailing slashを追加する挙動になる。
そのため、直接URLにアクセスした場合、Cloudflareのリダイレクトが発生し、最終的に useRoute().path が /blog/2025-10-08/ を返してしまう。これがNuxt Contentのパス(/blog/2025-10-08)と一致しないため、queryCollection が記事を見つけられなかった。
最初の対応
最初は、アプリケーション側でtrailing slashを除去する処理を追加した。
<script setup lang="ts">
const route = useRoute();
const path = route.path.replace(/\/$/, ''); // これを追加
const { data: article } = await useAsyncData(path, () =>
queryCollection('blog').path(path).first()
);
</script>
これでtrailing slashありのURLにアクセスした場合も、正しいパスで記事を取得できるようになった。
しかし、トップページから遷移した場合はtrailing slashなし、検索エンジンから直接アクセスした場合はtrailing slashありのURLになってしまうため、根本的な解決とは言えなかった。
根本的な解決
その後、「Cloudflareがリダイレクトしてるんだから、Cloudflare側に何か設定があるだろ」と調べていったところ、html_handlingの設定の中でtrailing slashの挙動を制御できることがわかった。
HTML handling
How to configure a HTML handling and trailing slashes for the static assets of your Worker.

上記を参考に、wrangler.jsoncにhtml_handling: "drop-trailing-slash"を追加したところ、どの経路からアクセスしてもuseRoute().pathがNuxt Contentのパス形式と一致するようになり、アプリケーション側の除去処理は不要になった。
最後に
冒頭で「そもそもアクセスが多いサイトでもないため放置」と書いてはいたが、実際のところ検索エンジンからの流入、つまり記事に直接アクセスされることもゼロではなかったので解決できて安心した。