Next.js + Vercel で日本語スラッグのページが全部404になる問題と対処法
ローカルでは動くのに本番だけ404。ファイル名に日本語が入っているページが軒並みアウトだった。
ローカル開発環境では問題なく表示されていたページが、Vercelにデプロイすると全部404になった。共通点を探したら、URLに日本語が含まれているページだけが落ちていた。
何が起きていたか
/blog/ギターの話 のような日本語パスのページが、本番環境でのみ404を返す。ローカルでは npm run dev でも npm run build && npm run start でも正常に動く。Vercelのログにも特にエラーは出ない。ページは静的生成されているはずなのに、アクセスすると404。
原因
generateStaticParams でスラッグを生成するとき、または動的ルートのパス解決でファイル名に日本語が含まれる場合、ファイル名のエンコードとURLのエンコードが二重になっていた。
典型的なパターンはこれだ。
// ❌ 問題のあるコード
export async function generateStaticParams() {
const posts = await getPosts();
return posts.map((post) => ({
slug: encodeURIComponent(post.slug), // ← これが問題
}));
}
generateStaticParams が返した値は Next.js によって自動的にURLエンコードされる。そこに encodeURIComponent で事前にエンコードした値を渡すと、%E3%82%AE%E3%82%BF%E3%83%BC が %25E3%2582%25AE%25E3%2582%25BF%25E3%2583%25BC になる。生成されたファイルと実際のリクエストURLが一致しなくなり、404になる。
ローカルの next dev は開発環境なのでこのズレを吸収してしまう。本番の静的ファイルサーバーは吸収しない。だからローカルでは気づけない。
対処
generateStaticParams には生の未エンコード値を返すだけでいい。
// ✅ 正しいコード
export async function generateStaticParams() {
const posts = await getPosts();
return posts.map((post) => ({
slug: post.slug, // そのまま返す
}));
}
日本語のスラッグがファイル名から来ている場合も同じ。fileToSlug のような変換関数を使っているなら、その中で encodeURIComponent を呼んでいないか確認する。
別のパターン:ファイル名をスラッグとして使う場合
Markdown ファイルのファイル名をそのままスラッグとして使っているケースで、日本語ファイル名(例:ギターの話.md)が混在していると同じ問題が起きる。
この場合の対処は2択だ。
- ファイル名をASCIIにする:
guitar-no-hanashi.mdのように英数字のみにする。frontmatter に日本語タイトルを別途書く。 - ファイル名からスラッグに変換するとき ASCII のみに絞る:日本語文字を除去またはローマ字変換して、スラッグはASCIIで扱う。
どちらでも動く。管理のしやすさで選ぶ。ファイル名に日本語を使いたい理由が特になければ、最初から英数字ハイフンで統一しておく方が後の問題を避けられる。
まとめ
generateStaticParamsに渡す値は生の未エンコード文字列を返す- 事前に
encodeURIComponentしない - ローカルは吸収してしまうので本番デプロイ後に発覚するケースが多い
- ファイル名スラッグ方式の場合はファイル名をASCIIにする方が安全
※ 本記事にはアフィリエイトリンクが含まれます。