응답이 다른 주소만 가리키고, 그 주소가 다시 여기로 오는 고리입니다. 브라우저는 본문을 그리지 못하고 한도에 도달해 이 줄을 냅니다. 서버 기록에는 성공이 거의 없고 옮겨 가라는 응답만 반복됩니다. 주소창이 깜빡이면 없는 페이지가 아니라 서로 밀어내는 규칙입니다.
로컬에서 안 보이는 이유는 앞단이 없어서입니다. 버셀 HTTPS 강제, 커스텀 도메인 www, next.config 슬래시 정책이 배포에만 붙습니다. 카카오 콘솔 리다이렉트 URI와 앱이 서로 슬래시를 다르게 적어도 로그인 뒤에만 돕니다.
CORS로 막힌 줄은 콘솔에 출처가 찍히고, 루프는 주소창이 깜빡입니다. 헤더 허용은 CORS입니다. 쿠키를 동기 호출로 읽어 경고가 나는 줄은 cookies await입니다. 여기는 Location 고리만 봅니다.
네트워크 탭 | Preserve log를 켜고 첫 요청부터 Location 헤더를 나란히 보세요. /와 /index, http와 https, www와 apex가 서로 밀어내는 쌍이 대부분입니다.
루프는 페이지가 없는 게 아니라 Location이 서로를 가리키는 줄이다
자주 도는 세 고리
슬래시, HTTPS, 로그인 보호 세 칸이면 원인이 갈립니다.
칸
증상
먼저 볼 곳
trailingSlash
/foo 와 /foo/ 가 왕복
next.config, 버셀 슬래시, canonical
HTTPS·www
http↔https, apex↔www
버셀 도메인 설정, 앱 redirects
로그인 미들웨어
/login 과 / 가 왕복
matcher, 쿠키 조건, 콜백 URL
OG·정적 파일
이미지만 308
skipTrailingSlashRedirect
카카오 콜백
로그인 뒤에만 루프
콘솔 URI와 앱 라우트 슬래시
넥스트는 끝 슬래시를 켜면 슬래시 없는 주소를 슬래시 있는 주소로 보냅니다. 앞단 배포망이 반대로 슬래시를 떼면 왕복입니다. 한국 검색 사이트에서 슬래시를 맞추려고 켠 설정이, 버셀 기본과 부딪히는 경우가 있습니다. 중간 처리의 경로 조건은 프록시 matcher 글과 자리가 겹치니, 루프면 옮겨 가기 주소부터 보고 경로 조건은 그다음입니다.
슬래시 왕복을 막는 설정 방향
// next.config.js
const nextConfig = {
trailingSlash: true,
skipTrailingSlashRedirect: true, // 정적 PNG·OG가 308로 안 돌게
}
module.exports = nextConfig
// 앱 redirects 와 호스트 HTTPS 강제가 서로 반대로 가리키지 않는지 확인
로그인 보호가 로그인 페이지를 다시 보낼 때
미들웨어가 "쿠키 없으면 /login"을 하고, 로그인 페이지도 같은 보호에 걸리면 왕복입니다. matcher에서 /login, /api/auth, 콜백 경로를 빼야 합니다. 카카오 콜백이 보호 안에 있으면 인가 코드가 도착해도 다시 로그인으로 밀립니다.
쿠키 SameSite 때문에 콜백에서 세션이 안 보이면, 미들웨어는 계속 비로그인으로 봅니다. 사파리에서만 루프면 쿠키 글입니다. 루프 자체는 Location이 남고, 쿠키 속성은 SameSite에서 다룹니다.
환경 변수가 프리뷰에서 비면 콜백 URL이 프로덕션을 가리켜 또 고리가 됩니다. NEXT_PUBLIC이 클라이언트에서 비는 줄은 환경변수 글입니다. 리다이렉트 URI는 콘솔에 프리뷰 도메인까지 등록돼 있어야 합니다.
로그인 경로를 matcher에서 빼기
export const config = {
matcher: [
'/((?!login|api/auth|_next/static|_next/image|favicon.ico).*)',
],
}
// 미들웨어 안에서
// 이미 /login 이면 다시 /login 으로 redirect 하지 말 것
[ ] 네트워크 탭에서 Location 쌍을 적었다
[ ] / 와 / 슬래시 왕복인지 봤다
[ ] http↔https, www↔apex인지 봤다
[ ] /login 이 matcher에 들어 있는지 봤다
[ ] 카카오 콘솔 URI와 앱 경로 슬래시를 대조했다
쿠키 없으면 로그인으로 보내면서 로그인 경로를 보호하면 고리가 된다
로컬은 되고 프리뷰만 돌 때
버셀 도메인 HTTPS 강제와 앱 redirects가 같은 일을 두 번 하면 고리가 납니다. 앱이 http를 https로 보내고, 플랫폼이 다시 반대로 고치는 설정은 없는지 봅니다. www를 apex로 보내는 규칙도 한곳만 맡깁니다.
OG 이미지와 favicon.ico가 308로 도는 건 본문 루프와 다릅니다. 슬래시 정책이 정적 파일까지 리다이렉트하면 네이버 수집이 실패합니다. skipTrailingSlashRedirect를 켜면 둘 다 200으로 남을 수 있습니다.
함수 시간 한도로 끊기는 줄은 FUNCTION_INVOCATION_TIMEOUT이고, 주소창이 도는 줄은 여기입니다. 502는 업스트림이 죽은 칸입니다. 루프는 업스트림이 살아 있고 헤더만 고리입니다.
보이는 것
실제
다음
ERR_TOO_MANY_REDIRECTS
3xx 고리
이 글
404
없는 경로
not-found 글
CORS 빨간 줄
브라우저가 응답 읽기 거절
CORS 글
로그인 무한 스피너
콜백 쿠키 실패일 수도
SameSite 글
한곳만 리다이렉트 | 플랫폼 도메인 설정과 next.config redirects와 미들웨어가 같은 규칙을 세 번 적으면 서로 반대 방향으로 밀기 쉽습니다. 슬래시·호스트·HTTPS는 한 칸만 남기세요.
실전에서 고치는 순서
Preserve log로 Location 목록을 적습니다. A→B→A면 그 쌍만 고칩니다. 슬래시면 config와 앞단을 맞추고, HTTPS면 플랫폼과 앱 중 하나를 끕니다. 로그인이면 matcher에서 콜백을 뺍니다.
curl -I로 브라우저 없이 헤더만 봐도 됩니다. 쿠키가 필요한 고리는 curl에 세션이 없어 다르게 보일 수 있으니, 로그인 루프는 브라우저 탭이 맞습니다. 시크릿 창은 확장이 끼지 않아 원인 가리기에 좋습니다.
고친 뒤에는 로컬뿐 아니라 미리보기 주소, www, 슬래시 있는 경로, 카카오 콜백까지 네 곳을 열어 보세요. 한 주소만 보면 왕복이 숨습니다. 예전에 보안 연결만 받겠다고 기억한 브라우저가 옛 규칙을 들고 있으면, 저장을 지워야 할 때도 있습니다. 그 헤더 자체는 보안 헤더 글입니다. 한국 검색 수집이 이미지를 못 가져오면 본문 루프가 아니라 정적 파일이 옮겨 가기 응답을 받은 칸을 같이 보세요.