301 vs 302 vs 307/308: 올바른 리다이렉트
리다이렉트 상태코드의 차이와 SEO·메서드 보존 관점에서 올바르게 고르는 법.
리다이렉트는 “이 주소는 다른 곳으로 옮겨졌다”고 브라우저와 검색엔진에 알리는 HTTP 응답이다. 응답 코드는 모두 3xx로 시작하고 새 위치는 Location 헤더에 담긴다. 겉보기에는 다 똑같이 “다른 페이지로 보내기”처럼 보이지만, 코드마다 (1) 이동이 영구인지 일시인지, (2) 원래 요청 메서드를 그대로 유지하는지, (3) 브라우저가 캐시해도 되는지가 다르다. 잘못 고르면 SEO 순위 신호가 새 주소로 넘어가지 않거나, POST 폼 전송이 조용히 GET으로 바뀌어 데이터가 사라지는 사고가 난다.
이 가이드는 5가지 리다이렉트 코드(301, 302, 303, 307, 308)의 차이를 표로 정리하고, SEO 관점의 301 vs 302, 메서드를 보존하는 307/308, 그리고 가장 흔한 실수(영구 이전에 302를 쓰는 것)를 실제 사례로 짚는다. 실제 서버가 어떤 코드를 돌려주는지 확인하려면 HTTP 상태 코드 확인 도구로 응답을 직접 찍어보면 된다.
5가지 리다이렉트 코드 한눈에 비교
핵심은 “메서드 보존”과 “영구/일시”의 조합이다. 아래 표에서 “메서드 유지”가 아니오인 코드는 비-GET 요청(주로 POST)을 자동으로 GET으로 바꿀 수 있다는 뜻이다.
| 코드 | 의미 | 메서드 유지 | 기본 캐시 | 주 용도(SEO) |
|---|---|---|---|---|
301 | 영구 이동 | 아니오(GET으로 변환 허용) | 예 | 도메인·URL 영구 변경, 순위 신호 이전 |
302 | 발견됨(일시) | 아니오(GET으로 변환 허용) | 아니오 | 점검·A/B·임시 이동(원본 색인 유지) |
303 | 기타(See Other) | 아니오(항상 GET으로) | 아니오 | POST 처리 후 결과 페이지로(PRG 패턴) |
307 | 임시 리다이렉트 | 예(메서드·본문 보존) | 아니오 | 일시 이동인데 POST를 그대로 보내야 할 때 |
308 | 영구 리다이렉트 | 예(메서드·본문 보존) | 예 | 영구 이동인데 API·POST를 보존해야 할 때 |
301 vs 302: SEO에서 무엇이 다른가
가장 자주 헷갈리는 쌍이다. 둘 다 “다른 주소로 보내기”지만 검색엔진이 해석하는 방식이 정반대다.
- 301(영구): “이 URL은 영원히 새 주소로 바뀌었다.” 검색엔진은 색인을 새 URL로 갱신하고, 기존 페이지가 쌓아온 링크·순위 신호를 새 URL로 전달한다. 도메인 변경,
http→https,www통일, 옛 글 URL 정리에는 301이 정답이다. - 302(일시): “원본은 그대로 있고 지금만 잠깐 다른 데로 보낸다.” 검색엔진은 원래 URL을 색인에 유지하려 한다. 점검 페이지, 지역·언어별 임시 우회, 단기 캠페인 페이지에 적합하다.
실무 규칙은 간단하다. 되돌릴 계획이 없으면 301/308, 곧 원래대로 돌릴 거면 302/307. 옮긴 뒤에는 응답 코드와 Location 헤더가 의도대로 나오는지 HTTP 헤더 확인 도구로 검증하자.
307·308이 필요한 이유: 메서드 보존
역사적으로 많은 브라우저가 301·302를 받으면 POST를 GET으로 바꾸고 요청 본문을 버렸다. 폼이나 API 호출이 리다이렉트를 만나면 데이터가 사라지는 것이다. 이 모호함을 없애기 위해 나온 게 307·308이다.
- 307 = 302의 “메서드 보존” 버전(임시).
POST는POST로, 본문은 그대로 새 URL에 다시 보낸다. - 308 = 301의 “메서드 보존” 버전(영구). 영구 이동이면서 메서드·본문을 유지한다. API 엔드포인트 영구 이전에 특히 안전하다.
- 303은 반대로 “무조건
GET으로 바꿔라”를 명시한다.POST로 데이터를 처리한 뒤 결과 페이지를GET으로 보여주는 PRG(Post/Redirect/Get) 패턴에서 새로고침 중복 제출을 막는 용도다.
흔한 실수와 점검 방법
- 영구 이전에 302 사용: 가장 흔한 사고. 검색엔진이 옛 URL을 계속 색인하고 순위 신호가 새 URL로 넘어가지 않아 트래픽이 정체된다. 영구 이동이면 반드시 301(또는 308).
- 리다이렉트 체인·루프:
A → B → C처럼 여러 단계를 거치면 신호가 희석되고 속도가 느려진다.A → A로 자기 자신을 가리키면 무한 루프가 난다. 항상 최종 목적지로 한 번에 보내자. - http→https에서 메서드 손실: API가 평문
http엔드포인트에POST를 보냈는데 301로 https로 보내면 일부 클라이언트에서 본문이 사라진다. 308을 쓰면 안전하다.
체인을 추적하려면 한 단계씩 따라가며 각 응답의 코드와 Location을 확인해야 한다. HTTP 상태 코드 확인으로 최종 코드를, HTTP 헤더 확인으로 Location과 캐시 헤더를 함께 보면 어디서 의도가 어긋났는지 바로 드러난다.
실전 예시: POST가 302에서 깨지고 308에서 살아남는 과정
결제 API //pay.example.com/charge가 //api.example.com/v2/charge로 옮겨졌다고 하자. 클라이언트는 카드 정보가 담긴 본문과 함께 POST를 보낸다.
302로 옮긴 경우(깨짐):
- 클라이언트:
POST /charge+ 본문(카드/금액) - 서버:
302 Found,Location: //api.example.com/v2/charge - 클라이언트(많은 구현): 메서드를
GET으로 바꾸고 본문을 버림 →GET /v2/charge - 새 엔드포인트: 본문 없는
GET수신 →400 Bad Request또는 빈 결제. 데이터 유실.
308로 옮긴 경우(보존):
- 클라이언트:
POST /charge+ 본문(카드/금액) - 서버:
308 Permanent Redirect,Location: //api.example.com/v2/charge - 클라이언트: 메서드와 본문을 그대로 유지 →
POST /v2/charge+ 동일 본문 - 새 엔드포인트: 정상 결제 처리 →
200 OK. 데이터 보존.
교훈: 비-GET 요청을 받는 엔드포인트의 영구 이전에는 301이 아니라 308을, 임시 우회에는 302가 아니라 307을 써야 한다. 적용 후에는 실제 응답이 308/307로 나오는지 HTTP 상태 코드 확인으로 꼭 확인하자.