Cloudflare Pages(Functions/Workers 実行)で、`/blog/20250810.mdx` のように Markdown/MDX の生テキストを配信したら、日本語が文字化けしました。ローカルの `astro dev` では再現せず、本番だけ壊れるやつ。 原因は単純で、レスポンスの `Content-Type`(MIME と charset)の扱いが微妙だったから。`text/mdx` のような独自っぽい MIME を返したり、charset 指定が弱いと、中継やブラウザ側で勝手に推測されて化けます。 今回の対処は Pages 標準のヘッダー設定ファイル `public/_headers` を使って、対象パスに強制的に `Content-Type: text/plain; charset=utf-8` を付ける、です。Workers のコードをいじらなくてもいいのがポイント。 参考: https://developers.cloudflare.com/workers/static-assets/headers/ 補足(このサイトの前提) このブログでは、各記事の元原稿(生の MDX)をそのまま公開しています。例えば、通常のページ `/blog/20250810` に対応して、`/blog/20250810.mdx` で原稿テキストを配信しています。生 MDX を直接ブラウザに見せるという性質上、`Content-Type` と `charset` の指定が弱いと簡単に文字化けします。今回の `_headers` はその前提を踏まえた対処です。 --- やったこと(結論) 1. `public/_headers` を用意し、`.mdx` 直配信のパスにヘッダーを付ける 2. ついでに `X-Content-Type-Options: nosniff` とキャッシュも良き感じに `public/_headers` の中身はこんな感じ。 ``` # /blog/*.mdx は「生のテキスト」として UTF-8 で配信する /blog/*.mdx Content-Type: text/plain; charset=utf-8 X-Content-Type-Options: nosniff Cache-Control: public, max-age=300, s-maxage=31536000, immutable ``` これだけで、Pages 経由のレスポンスに上記ヘッダーが必ず乗るようになります。Astro のエンドポイントで `Response` を返していても、Pages の `_headers` が最終的な上書きをしてくれるため、CDN 側での推測(sniff)が抑制され、文字化けが止まります。 --- 背景メモ - ローカルでは OK、本番(Workers 経由)でだけ文字化け → 中継や CDN が `Content-Type` を解釈できず、勝手に推測した可能性が高い。 - `text/mdx` は一般的でないため、`text/plain; charset=utf-8` に寄せたら安定した。 - 本ブログの生MD/MDX配信はビルドで静的化されるため、実行時に個別エンドポイントで細かく調整することはできない。Cloudflare Pages の `_headers` を使う前提。 --- Astro側で直接書くならこんな感じという参考コード。ただし、このブログの「生MD/MDX直配信」には適用できません(ビルドで静的ファイルになり、実行時のエンドポイントが存在しないため)。必ず `_headers` を使います。 ```ts export const GET = async () => { const body = "# Hello MDX\n日本語テスト 🐤"; return new Response(body, { headers: { "content-type": "text/plain; charset=utf-8", "x-content-type-options": "nosniff", }, }); }; ``` --- まとめ Pages/Workers で「プレーンな Markdown/MDX をそのまま見せたい」場合、`public/_headers` による `Content-Type: text/plain; charset=utf-8` の強制が一番手軽で安全でした。特にこのブログ構成では、Astro のビルドで `/blog/*.mdx` が静的出力(例: `dist/blog/20250810.mdx`)され、`_worker.js/pages/blog/...` のエンドポイントは生成されません。そのため実行時にヘッダーを細かく調整する余地はなく、Cloudflare Pages の `_headers` で上書きするのが必須です。