본문으로 건너뛰기
MintSoft
  • 불러오는 중입니다.

commit 205f236

한글 주소가 운영에서만 500을 낸 이유

로컬에서는 멀쩡한 한글 태그 페이지가 배포 환경에서만 실패했습니다. 원인은 캐시 정보가 담기는 HTTP 헤더였습니다.

· 3분 읽기

목차

이 블로그를 처음 배포한 날, 태그 페이지에서 이상한 일이 있었습니다. 영문 태그는 정상인데 한글 태그만 500 오류가 났습니다. 로컬에서는 한글 태그도 멀쩡했습니다.

증상

정리하면 이랬습니다.

  • 배포 환경에서 한글이 들어간 주소만 500이 났습니다.
  • 글이 없는 한글 태그를 요청해도 404가 아니라 500이었습니다.
  • 로컬에서는 재현되지 않았습니다.

마지막 항목이 가장 까다로웠습니다. 로컬에서 재현되지 않으니 코드를 아무리 읽어도 단서가 없었습니다.

원인

운영 로그를 보고 나서야 원인을 찾았습니다. Next.js는 ISR로 캐시하는 페이지의 경로를 x-next-cache-tags 응답 헤더에 넣습니다. 그런데 경로에 한글이 있으면 이 값이 HTTP 헤더에 넣을 수 없는 문자가 되어 응답이 실패했습니다.

HTTP 헤더는 원칙적으로 ASCII를 전제로 합니다. 주소창에 보이는 한글은 전송될 때 퍼센트 인코딩되지만, 프레임워크가 디코딩한 경로를 헤더에 그대로 쓰면 이야기가 달라집니다.

해결

페이지의 성격에 따라 두 가지로 나눠 처리했습니다.

태그 페이지는 캐시를 포기했습니다. 태그는 한글을 써야 자연스러우므로 주소를 영문으로 바꿀 수 없습니다. 그래서 요청마다 렌더링하도록 바꿨습니다.

ts
export const dynamic = "force-dynamic";

글 주소는 영문만 허용했습니다. 글 페이지는 캐시의 이득이 크고, 주소를 영문으로 정해도 잃는 것이 없습니다. 형식에 맞지 않는 주소는 페이지에 닿기 전에 404로 돌려보냅니다.

ts
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 문자가 들어가는지 먼저 확인해 보세요.