Next.js Middleware Matcher에 변수를 사용할 수 없는 이유
Next.js middleware matcher에 변수를 사용했을 때 발생하는 문제와 그 원인을 Next.js의 소스 코드 분석을 통해 알아보자.
특정 페이지에서 JavaScript가 실행되지 않고 화면이 깨지는 현상이 발생했다. 흥미로운 점은 middleware matcher에 정의된 여러 경로 중 /signup 페이지에서만 이 문제가 나타났다는 것이다.

아래는 실제로 사용된 config 이다.
export const config = {
matcher: [
'/',
'/account',
Path.ITEMS,
'/blog/:path*',
'/user/:path*',
'/signin',
'/signup',
// ...
]
};Next.js 공식 문서에 따르면, middleware matcher의 값은 빌드 시점에 정적으로 분석할 수 있어야 하며, 변수와 같은 동적 값은 무시된다고 설명되어 있다.
처음에는 Path.ITEMS를 config 바로 위에 const로 선언했기 때문에 문제가 없을 것이라 생각했다. 하지만 실제로는 문제가 발생했고, 그 이유를 찾기 위해 Next.js의 소스 코드를 분석해보았다.
아래는 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 = 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 {
for (const moduleItem of module.body) {
if (!isExportDeclaration(moduleItem)) continue;
const declaration = moduleItem.declaration
if (!isVariableDeclaration(declaration)) continue;
if (declaration.kind !== 'const') continue;
// ...
}
}Next.js는 SWC(Speedy Web Compiler)로 코드를 파싱하고 AST를 생성한다. 이 과정에서 변수를 참조하는 설정은 값을 확정할 수 없기 때문에 정적 분석에서 제외된다. 결과적으로 해당 경로는 matcher에 포함되지 않고 빌드된다.
올바른 설정은 다음과 같이 문자열을 직접 사용하는 것이다.
// ✅ 올바른 예
export const config = {
matcher: [
'/',
'/account',
'/items',
'/blog/:path*',
]
};왜 다른 페이지들은 정상 작동하고 /signup 페이지에서만 문제가 발생했는지에 대해서는 알아내지 못했다. 이는 아마도 Next.js의 빌드 프로세스나 코드 분할(code splitting) 과정에서의 특이점과 관련이 있을 것으로 추측된다.
Next.js Middleware의 matcher는 우리가 작성하는 일반적인 JavaScript 코드처럼 런타임에 평가되는 것이 아니라, 빌드 타임에 텍스트 그 자체로 분석된다.
조금 번거롭더라도 matcher 설정만큼은 변수나 공통 상수를 사용하지 말고 명시적인 문자열 배열로 관리해야 한다.