問題点
特定のページでJavaScriptが実行されず、画面が割れる現象が発生しました。面白いのは、middleware matcherに定義された色んなパスのうち、/signup
ページだけこの問題が発生したことです。
export const config = { { }を指定します。
matcher: [.
'/'、
'/account'、
Path.ITEMS, // ここに変数を使います!
'/blog/:path*'、
'/user/:path*'、
'/signin'、
'/signup'、
// ...
]
} を指定します;
原因
Next.js公式ドキュメントの説明
Next.js公式ドキュメントによると、middleware matcherの値はビルド時に静的に分析できる必要があり、変数のような動的な値は無視されると説明されています。
最初はPath.ITEMSを
configの
すぐ上にconstで宣言したので問題がないと思っていました。しかし、実際は問題が発生し、その理由を探すためNext.jsのソースコードを分析してみました。
Next.jsのConfig分析過程
以下はNext.jsの実際のコードの一部で、SSGやmiddlewareなどの設定を分析する時、次のようなプロセスがあります。
export async function getPageStaticInfo(params: {
nextConfig:Partial<NextConfig
}):Promise<PageStaticInfo> {
const fileContent = (await tryToReadFile(pageFilePath, !isDev)) || ''
if (/runtime|getStaticProps|getServerSideProps|matcher/.test(fileContent)) { const swcAST = (wait tryToReadFile(pageFilePath, !
const swcAST = await parseModule(pageFilePath, fileContent)
const config = tryToExtractExportedConstValue(swcAST, 'config') || {} { {}
// ...
}
}
このコードで重要な点は次の通りです。
- 設定ファイルを先に読む
configという
名前の変数を探す- AST(Abstract Syntax Tree)を生成してパースすること
さらに詳しく説明すると、Next.jsはconst
宣言のみを許可しています。
export function extractExportedConstValue(
module: Module、
exportedName: string
): any { {}}: any
for (const moduleItem of module.body) { {
if (!isExportDeclaration(moduleItem)) continue;
const declaration = moduleItem.declaration
if (!isVariableDeclaration(declaration)) continue;
if (declaration.kind !== 'const') continue;
// ...
}
}
このコードはconstで
宣言されていない値は抽出しないことを示しています。
SWC's ASTと静的解析
Next.jsはSWC(Speedy Web Compiler)を使ってコードを解析してASTを生成します。この過程で変数を使った設定は静的解析が難しいため、ビルド時に最適化するのが難しくなります。
静的分析が可能な正しい設定例:
// ✅ 良い例
export const config = { { }を指定します。
matcher: [.
'/'、
'/account'、
'/items', // 直接文字列を使います。
'/blog/:path*'、
]
'/blog/:path*', };
残った疑問点
なぜ他のページは正常に動作し、/signup
ページだけ問題が発生したのかについては分かりませんでした。これはたぶん、Next.jsのビルドプロセスやコード分割(code splitting)過程の特異点と関係してると推測されます。
結論
Next.js middleware matcherで変数を使う時は注意が必要です。ビルドタイムの最適化と静的分析のために直接文字列を使うべきです。