为什么middleware的matcher不能使用变量

profile image

通过分析Next.js源代码,探讨在Next.js middleware matcher中使用变量时出现的问题及其原因。

本帖由 DeepL 翻译。如有任何翻译错误,请告知我们!

问题

在特定页面上出现了JavaScript无法执行且屏幕显示异常的现象。有趣的是,这个问题只在middleware matcher定义的多个路径中的/signup页面上出现。

Image.png

typescript
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等配置时的过程:

typescript
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 = await parseModule(pageFilePath, fileContent)
    const config = tryToExtractExportedConstValue(swcAST, 'config') || {}
    // ...
  }
}

这段代码中的重要点如下:

  1. 首先读取配置文件
  2. 寻找名为config的变量
  3. 生成并解析AST(抽象语法树)

更详细地看,Next.js只允许const声明:

typescript
export function extractExportedConstValue(
  module: Module,
  exportedName: string
): 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的AST和静态分析

Next.js使用SWC(Speedy Web Compiler)来解析代码并生成AST。在这个过程中,使用变量的配置难以进行静态分析,因此在构建时难以优化。

可以进行静态分析的正确配置示例:

typescript
// ✅ 好的示例
export const config = {
  matcher: [
    '/',
    '/account',
    '/items', // 直接使用字符串
    '/blog/:path*',
  ]
};

遗留问题

我无法确定为什么其他页面正常工作,而只有/signup页面出现问题。这可能与Next.js的构建过程或代码分割(code splitting)过程中的特殊性有关。

结论

在使用Next.js middleware matcher时需要注意。为了构建时优化和静态分析,应该使用直接的字符串字面量。

❤️ 0
🔥 0
😎 0
⭐️ 0
🆒 0