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` で上書きするのが必須です。