이 블로그를 처음 배포한 날, 태그 페이지에서 이상한 일이 있었습니다. 영문 태그는 정상인데 한글 태그만 500 오류가 났습니다. 로컬에서는 한글 태그도 멀쩡했습니다.
증상
정리하면 이랬습니다.
- 배포 환경에서 한글이 들어간 주소만 500이 났습니다.
- 글이 없는 한글 태그를 요청해도 404가 아니라 500이었습니다.
- 로컬에서는 재현되지 않았습니다.
마지막 항목이 가장 까다로웠습니다. 로컬에서 재현되지 않으니 코드를 아무리 읽어도 단서가 없었습니다.
원인
운영 로그를 보고 나서야 원인을 찾았습니다. Next.js는 ISR로 캐시하는 페이지의 경로를 x-next-cache-tags 응답 헤더에 넣습니다. 그런데 경로에 한글이 있으면 이 값이 HTTP 헤더에 넣을 수 없는 문자가 되어 응답이 실패했습니다.
HTTP 헤더는 원칙적으로 ASCII를 전제로 합니다. 주소창에 보이는 한글은 전송될 때 퍼센트 인코딩되지만, 프레임워크가 디코딩한 경로를 헤더에 그대로 쓰면 이야기가 달라집니다.
해결
페이지의 성격에 따라 두 가지로 나눠 처리했습니다.
태그 페이지는 캐시를 포기했습니다. 태그는 한글을 써야 자연스러우므로 주소를 영문으로 바꿀 수 없습니다. 그래서 요청마다 렌더링하도록 바꿨습니다.
export const dynamic = "force-dynamic";
글 주소는 영문만 허용했습니다. 글 페이지는 캐시의 이득이 크고, 주소를 영문으로 정해도 잃는 것이 없습니다. 형식에 맞지 않는 주소는 페이지에 닿기 전에 404로 돌려보냅니다.
const SLUG = /^[a-z0-9]+(-[a-z0-9]+)*$/;
export function proxy(request: NextRequest) {
const slug = request.nextUrl.pathname.slice("/posts/".length);
if (SLUG.test(slug)) return NextResponse.next();
return new NextResponse("Not Found", { status: 404 });
}
글을 저장할 때도 같은 규칙으로 검사합니다. 입력과 출력의 규칙이 같아야 목록에는 있는데 열면 404인 글이 생기지 않습니다.
배운 점
- 로컬에서 재현되지 않으면 운영 로그부터 봅니다. 이번에도 코드보다 로그 한 줄이 빨랐습니다.
- 캐시는 기능이 아니라 또 하나의 실행 경로입니다. 캐시가 켜진 경로에는 그 경로만의 제약이 있습니다.
- 주소 규칙은 일찍 정합니다. 글이 쌓인 뒤에는 주소를 바꾸기 어렵습니다.
프레임워크 버전이 바뀌면 이 동작도 달라질 수 있습니다. 비슷한 증상을 만났다면 응답 헤더에 비ASCII 문자가 들어가는지 먼저 확인해 보세요.
