Cloudflare Pages/WorkersでMD/MDXを文字化けなく配信するには_headersを使う
ブラウザでの文字ばけを防ぐため、public/_headers で強制的に UTF-8 を流す
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
はその前提を踏まえた対処です。
やったこと(結論)
public/_headers
を用意し、.mdx
直配信のパスにヘッダーを付ける- ついでに
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
を使います。
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
で上書きするのが必須です。