정의
내 도메인의 /blog에 들어갔더니 페이지는 안 뜨고 브라우저가 "리다이렉트가 너무 많습니다"로 포기한 적이 있다면, 그런데 앱 코드에는 리다이렉트가 한 줄도 없다면 이 개념이 필요하다.
rewrite(리라이트) 는 주소창을 바꾸지 않고 서버가 대신 업스트림에서 내용을 받아다 전달하는 프록시다. 브라우저에게 "저기로 다시 가라"고 알려 주는 redirect(리다이렉트, 3xx) 와는 다르다. 핵심 함정은 rewrite가 업스트림 응답을 가공하지 않는다는 점이다 — 업스트림이 3xx를 주면 그 3xx가 그대로 클라이언트에 전달된다. 업스트림이 캐노니컬 도메인(대표 주소)으로 되돌리는 SaaS라면, 커스텀 도메인 → 오리진 → 커스텀 도메인의 무한 왕복이 만들어진다.
왜 필요한가
"블로그·문서·상태 페이지만 SaaS에 맡기고 내 도메인의 하위 경로처럼 보이게 한다"는 구성은 매우 흔하다. 이때 rewrite를 "내부적으로 몰래 가져오는 것"으로만 이해하면 3xx 전달을 예측하지 못한다.
많은 SaaS는 SEO 중복 URL을 막으려고 테넌트 주소(saas.com/<tenant>)로 들어온 요청을 고객 대표 도메인(mydomain.com/blog)으로 308 이동시킨다. 그런데 그 대표 도메인이 다시 rewrite로 SaaS를 가리키므로 요청이 끝없이 돈다. 증상은 ERR_TOO_MANY_REDIRECTS이고, 리다이렉트 코드는 내 레포 어디에도 없으므로 원인 추적이 어렵다.
동작 원리
- 브라우저가
https://mydomain.com/blog를 요청한다. - 애플리케이션 서버가 rewrite 규칙에 따라
https://saas.com/tenant로 서버 간 요청을 보낸다. - SaaS가 캐노니컬 정책에 따라
308 Location: https://mydomain.com/blog를 응답한다. - rewrite는 프록시이므로 이 308을 가공 없이 브라우저에 전달한다.
- 브라우저가
Location을 따라 1번으로 돌아간다 → 무한 루프.
| 항목 | redirect | rewrite |
|---|---|---|
| 주소창 | 바뀐다 | 그대로 |
| 응답 | 3xx를 직접 생성 | 업스트림 응답을 전달 |
| 업스트림 3xx | 해당 없음 | 그대로 통과 |
| 루프 위험 | 규칙끼리 순환할 때 | 업스트림이 나를 가리킬 때 |
실무 적용
먼저 목적지가 프록시 가능한지 추측하지 말고 확인한다.
curl -I --max-redirs 0 https://saas.com/tenant
# HTTP/2 308
# location: https://mydomain.com/blog ← 내 도메인을 가리킨다 = 루프 확정
curl -I --max-redirs 0 https://proxy.saas.dev/tenant
# HTTP/2 200 ← 프록시 목적지로 적합
대부분의 SaaS는 커스텀 도메인 테넌트용으로 캐노니컬 리다이렉트를 하지 않는 별도 호스트를 제공한다. 목적지를 그쪽으로 바꾼다.
// next.config.mjs
async rewrites() {
return {
afterFiles: [
{ source: "/blog", destination: "https://proxy.saas.dev/tenant" },
{ source: "/blog/:path*", destination: "https://proxy.saas.dev/tenant/:path*" },
],
};
}
같은 설정 파일에 이미 정상 동작하는 형제 규칙이 있다면 그 목적지 호스트가 정답인 경우가 많다. 깨진 규칙만 보지 말고 옆 줄을 읽는다.
트레이드오프
- rewrite(프록시): 경로가 내 도메인에 통합돼 SEO·쿠키·분석이 한 오리진으로 모인다. 대신 모든 트래픽이 내 서버를 경유해 지연과 비용이 늘고, 업스트림 응답 헤더·상태 코드를 그대로 떠안는다.
- subdomain CNAME 위임(
blog.mydomain.com→ SaaS): 프록시 비용이 0이고 루프도 없다. 대신 경로가 분리돼 하위 도메인으로 취급되고, 쿠키·분석이 갈린다. - redirect: 가장 단순하지만 사용자에게 SaaS 주소가 그대로 노출된다.
사용하면 안 되는 경우
- 업스트림이 캐노니컬 리다이렉트를 하고, 벤더가 프록시 전용 호스트를 제공하지 않는 경우. 이때는 rewrite를 포기하고 서브도메인 위임으로 간다.
/robots.txt,/sitemap.xml처럼 사이트 전역을 대표하는 리소스를 하위 서비스로 프록시하는 경우. 블로그용 robots가 사이트 전체 robots를 덮어써 색인 정책이 통째로 뒤집힌다.- 업스트림이 인증 쿠키를 자기 도메인으로 발급하는 경우 — 프록시를 통과하면 쿠키 도메인이 어긋나 로그인 상태가 유지되지 않는다.
흔한 실수
- rewrite를 redirect로 이해하고 업스트림 3xx가 통과된다는 사실을 놓친다. 루프의 절대다수가 여기서 나온다.
- 남의 설정 스니펫을 통째로 붙여넣는다. 필요한 건 목적지 호스트 한 줄인데, 통째 적용하면 기존 캐치올 rewrite·이미지·번들 설정이 사라져 사이트 전체가 죽는다.
- 불필요하게
beforeFiles로 옮긴다. 같은 경로의 로컬 라우트가 없으면afterFiles와 동작이 같다. 이유 없이 옮기면 diff만 커지고 우선순위 사고가 생긴다. - 브라우저 캐시에 속는다. 308은 영구 리다이렉트라 브라우저가 캐시한다. 수정 후 검증은 시크릿 창이나
curl로 한다.
관련 개념
- ssrf-url-fetch-proxy-guard — 서버가 남의 URL을 대신 가져올 때의 목적지 검증
- locale-aware-i18n-routing — 경로 기반 라우팅과 rewrite가 겹치는 층
- etag-conditional-request-304 — 프록시 경유 시 조건부 응답 처리