📝 Next.js App Router で Dynamic Routes が本番だけで404になるバグと向き合う

※ ただの私的なメモです。


Next.js のプロダクトにおいて、`src/app/[test]/page.tsx` という動的ルーティングのページを作る。

export default function ActivityPage({ params }: { params: { test: string } }) {
  return <>{params.test}</>;
}

開発環境ではパス名が表示される

が、本番 (Vercel) では404エラーになる

面白いことに同ページでは favicon.ico のリクエストも404になる

dev console のヘッダー情報

ルートパスや動的パス以外の画面は、NextAuth の認証や tRPC の疎通含めて正常にできている

動的なパス設定の場合のみ。page コンポーネントに ClientSide コンポーネントを含めているとか関係ない

ただ、src/app/layout.tsx はその限りではない。ServerSide も ClientSide もごちゃ混ぜになっている。

import { type Metadata } from "next";
import "./globals.css";
import { TrpcProvider } from "./TrpcProvider";
import ThemeRegistry from "./ThemeRegistry";
import { ClientSessionProvider } from "./ClientSessionProvider";
import { font } from "./theme";

export const metadata: Metadata = {
  title: "ルーティンナさん | Routinena",
  description:
    "「ルーティンナさん」はあなたの定期的なタスクを追跡し、最後にいつ完了したのかを簡単に記録できるWebアプリです。あなたの忙しい日常をサポートし、タスク管理を効率的にします。",
  viewport: {
    width: "device-width",
    initialScale: 1,
    maximumScale: 1,
  },
  themeColor: [
    { media: "(prefers-color-scheme: light)", color: "#FEFCF7" },
    { media: "(prefers-color-scheme: dark)", color: "#40221B" },
  ],

  // PWA config
  manifest: "/manifest.webmanifest",

  // <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
  // <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
  // <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
  icons: {
    icon: [
      { url: "/icons/favicon-16x16.png", sizes: "16x16", type: "image/png" },
      { url: "/icons/favicon-32x32.png", sizes: "32x32", type: "image/png" },
    ],
    apple: { url: "/icons/apple-touch-icon.png", sizes: "180x180" },
  },
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="ja" suppressHydrationWarning>
      <body className={font.className}>
        <ClientSessionProvider>
          <ThemeRegistry>
            <TrpcProvider>{children}</TrpcProvider>
          </ThemeRegistry>
        </ClientSessionProvider>
      </body>
    </html>
  );
}

Next.js Dynamic Routes ドキュメント


プレーン環境の試行

Issue を漁ってみたのは後でまとめるとして、最大の原因鑑別ということで、プレーンな Next.js の環境を用意して Vercel で公開してみると、期待通り動作した。どちらも現時点で最新バージョンの 13.4.10 を使っている

この鑑別から、 layout.tsx か create-t3-app でセットアップされた設定周りが怪しい

next.config.mjs はあまり変わらなかった。 `reactStrictMode: true` の指定が新たに入っていたが、消しても改善せず


関連 Issue

現状オープンな関連していそうな Issue はこれ

結論解決はされていないっぽいし原因も特定されていない?みたいだ。回避策としては以下の記述が見つかる

> Hi, we are having the same problem, it treats dynamic route as it does not exist and redirects to root page.
> Temporary fix (but ugly one) is to use useSearchParams and instead of item/123 to use item?id=123

This IMO is the best “workaround”. Everyone can still understand this model, and does not require infra changes

> こんにちは、私たちも同じ問題を抱えています。ダイナミックルートが存在しないものとして扱われ、ルートページにリダイレクトされてしまいます。
> 一時的な修正(しかし、醜いもの)は、useSearchParamsを使用し、item/123の代わりにitem?id=123を使用することです。

これが最善の "回避策 "です。誰もがこのモデルを理解することができますし、インフラを変更する必要もありません。

https://github.com/vercel/next.js/issues/48022#issuecomment-1591016333
翻訳は
 DeepL による

他にも関連しそうな Issue はあったが、どれも関係なさそうだった


原因は i18n の設定だった

と思ったら解決した。create-t3-app で指定されていた i18n の設定だった。

next.config.mjs

/**
 * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful
 * for Docker builds.
 */
await import("./src/env.mjs");

/** @type {import("next").NextConfig} */
const config = {
  reactStrictMode: true,

  /**
   * If you have `experimental: { appDir: true }` set, then you must comment the below `i18n` config
   * out.
   *
   * @see https://github.com/vercel/next.js/issues/41980
   */
  i18n: {
    locales: ["en"],
    defaultLocale: "en",
  },
};

export default config;

config.i18n を削除すると改善した


全然読んでなかったが appDir (App Routing) の環境では設定をコメントアウトするように注意書きがある・・・。開発環境で動くのが解せないが、書いてあるということはそうなんだろう。

斜め読みしたが今回の症状に対して直接的な記述はないように見受けられる。関連しそうな記述はここら辺か

@aej11a Yeah, I've tried that, but that just gives a 404.
I also tried [...locale] and [[...locale]] which works for direct pages. But sub folders will throw an Catch-all must be the last part of the URL. error

I would be of benefit of allowing sub-folders under a catch-all to make internationalization easier. Will have to wait for the v13 docs to see what they suggest regarding middleware.

update: It also seems middleware isn't being loaded

https://github.com/vercel/next.js/issues/41980#issuecomment-1293627764

同じ人から気になる記述が。404.tsx を追加したら治ったとのこと

@johnkahn yeah, adding my 404.tsx into the pages directory has middleware working now and can access everything in the request.

https://github.com/vercel/next.js/issues/41980#issuecomment-1294046496

Next.js のドキュメントはこれ


ただドキュメント通り not-found.tsx を作ったら本番ビルドでエラー (開発環境では通る)

Error: ENOENT: no such file or directory, open '/vercel/path0/.next/server/pages/ja/404.html'
Error: ENOENT: no such file or directory, open '/vercel/path0/.next/server/pages/en/404.html'

コメント通り 404.tsx に改名するとビルドは通ったが 404 エラー時の画面はデフォルトになる (コンポーネントが使用されない)。そして本番環境下で動的ルーティングが404になるエラーは改善しなかった。やはり next-config の i18n 設定が App Router に対応していない? か別仕様に変わったのかも知れない。直近では対応する予定がないのでここでは探りはしない


まとめ

ということで、原因は i18n の設定だった。本番だけで発生する症状だったので調べるのに手間取ってしまった

App Router (App Directory) は RSC の導入など、メリットが大きそうな一方でやはりというか当然のように破壊的な変更であった。もはや別フレームワークと認識した方が良いほどではないだろうか。それにしても良いが。


まだ周辺ライブラリの対応状況は実験的だったり追いついていないものが見受けられるが、 "use client"; ディレクティブでどうにかなる場合も多いので、やろうと思えば本番にも導入できるのではないだろうか。SSRが必須要件だった場合、かなりきつめの制約がいくつか発生するかも知れないが

この記事が気に入ったらサポートをしてみませんか?