URL을 바꾸면 필연적으로 죽은 옛 주소가 무더기로 생긴다. 내 경우 수천 개였다. 문제는 구글이 한 번 발견한 URL을 쉽게 안 잊는다는 것이다. 색인에서 빠진 뒤에도 혹시 살아났나 하고 계속 재방문한다. 그 결과 신생 도메인의 쥐꼬리만 한 크롤 예산이, 새 글이 아니라 이미 시체가 된 주소를 확인하는 데 낭비된다.


핵심은 HTTP 상태 코드가 사람이 아니라 봇에게 거는 말이라는 점이다. 화면에 뭐라고 보여주는지와 전혀 별개로, 서버가 헤더로 돌려주는 세 자리 숫자가 구글의 행동을 결정한다. 사용자는 예쁜 없음 페이지를 보더라도, 그 뒤에서 서버가 봇에게 정확한 숫자를 말해줘야 한다. 이 언어를 제대로 쓰는 것만으로 낭비되던 크롤 예산을 상당히 회수할 수 있다. 이번 편은 그 방법이다.


[실전 SEO 06] 죽은 옛날 주소 치우는 법, 404 vs 410, 리다이렉트의 함정


1. 404와 410의 결정적 차이

둘 다 페이지가 없다는 뜻이지만 뉘앙스가 다르다. 404 Not Found는 지금은 없다인데, 여기엔 나중에 다시 생길 수도 있다는 여지가 은근히 들어 있다. 그래서 구글은 404를 받은 URL을 한동안 미련을 갖고 주기적으로 재방문한다. 혹시 부활했을까 봐 계속 확인하러 오는 것이다.


반면 410 Gone은 이건 영구적으로 사라졌다는 명시적 선언이다. 구글은 410을 받으면 훨씬 빠르게 색인에서 제거하고 재방문 빈도도 급격히 줄인다. 그래서 확실히 폐기한 옛 주소는 404로 방치하지 말고 410으로 명시하는 게 크롤 예산 회수에 유리하다. 물론 구글도 404가 아주 오래 지속되면 결국 410처럼 취급하긴 하지만, 그 시간을 며칠이라도 앞당기는 게 예산이 빠듯한 신생 도메인에겐 소중하다. 확실한 삭제엔 확실한 신호를 주자.


상태 코드를 제대로 쓰기 시작하면 크롤 통계에서 그 효과가 눈에 보인다. 404와 410 요청 비율이 서서히 줄고 200 비율이 올라간다. 봇이 시체 주소를 확인하는 데 쓰던 예산이 살아 있는 새 글로 옮겨간다는 뜻이다. 숫자 세 자리를 바로잡았을 뿐인데 크롤 효율이 달라지는 걸 보면 상태 코드의 힘을 실감하게 된다.


2. 소프트 404라는 조용한 독

가장 피해야 할 함정이 소프트 404다. 페이지가 실제로는 없는데 서버가 상태 코드는 200 OK를 주면서 본문에는 이 글은 존재하지 않습니다 같은 안내만 띄우는 경우다. 사람 눈엔 없음 페이지처럼 보이지만 구글에겐 멀쩡한 정상 200 응답으로 잡힌다. 겉과 속이 다른 것이다.


구글은 이런 알맹이 없는 200이 쌓이면 사이트 전체의 품질 평가를 깎는다. 내용 없는 페이지를 200으로 계속 내놓는 사이트로 학습되기 때문이다. 없으면 없다고 정직하게 상태 코드로 말해야 한다. 안내 문구를 아무리 예쁘게 보여주더라도 HTTP 상태는 반드시 404나 410이어야 한다. 특히 SPA에서 이 실수가 잦다. 클라이언트에서 없는 글이라고 화면만 바꾸고 서버 응답은 200으로 두면 전형적인 소프트 404가 된다. 없는 콘텐츠는 반드시 서버 단에서 상태 코드까지 바꿔줘야 한다.


소프트 404를 자가 점검하는 방법도 간단하다. 없는 글 주소로 curl을 날려 응답 헤더의 상태 코드를 직접 확인하면 된다. 화면엔 없음 페이지가 떠도 헤더가 200이면 그게 바로 소프트 404다. GSC의 소프트 404 항목에 잡히기 전에, 배포할 때마다 이 한 줄 확인을 습관으로 해두면 사전에 막을 수 있다.


상태 코드를 다룰 때 캐시 헤더도 함께 신경 써야 한다. 404나 410 응답이 과도하게 오래 캐시되면 그 주소를 되살리고 싶어졌을 때 옛 응답이 발목을 잡을 수 있다. 반대로 캐시가 아예 없으면 매 요청이 서버까지 온다. 없어진 게 확실한 주소는 적당히 캐시하되 되살릴 여지가 있는 건 짧게 두는 식으로 상황에 맞춰야 한다.


3. 리다이렉트 체인은 최악의 낭비

진짜 최악은 옛 주소를 새 주소로 리다이렉트했는데 그 새 주소에도 글이 없어서 결국 404로 끝나는, 빙빙 도는 구조다. 구글봇은 리다이렉트를 한 번 따라가고, 힘들게 도착한 곳에서 또 404를 받는다. 한 URL을 처리하는 데 크롤 예산을 두 배로 쓰는 셈이고, 봇 입장에선 헛걸음을 두 번 한 것이다.


게다가 이런 패턴이 많으면 사이트 관리가 엉성하다는 신호까지 준다. 원칙은 아주 간단하다. 대체할 콘텐츠가 있으면 그 자리로 한 번에 301, 대체할 게 없으면 리다이렉트 걸지 말고 그 자리에서 바로 410으로 끝낸다. 없는 걸 어딘가로 보내려는 어설픈 친절이 오히려 봇을 두 번 헛걸음시킨다. 죽은 주소에겐 다른 데로 가라가 아니라 여긴 끝났다고 말해주는 게 맞다.


리다이렉트 체인은 도구로 잡는 게 정확하다. 옛 URL 목록을 훑으며 각각이 최종적으로 어떤 상태 코드에 도달하는지 따라가는 스크립트를 돌리면, 2홉 이상이거나 404로 끝나는 문제 URL이 목록으로 나온다. 눈으로 하나씩 확인하는 건 몇천 개 규모에선 불가능하니, 이런 점검은 반드시 자동화해두는 게 좋다.


체인을 없애는 실무 팁 하나. 리다이렉트 규칙을 새로 추가할 때 항상 최종 목적지를 기준으로 규칙을 쓰는 것이다. 옛 주소 A를 B로 보냈다가 나중에 B가 C로 옮겨졌다면, A의 규칙도 곧바로 C를 가리키게 갱신한다. 이렇게 최종점 기준으로 관리하면 체인이 애초에 자라나지 않는다.


4. 언제 301이고 언제 410인가

판단 기준은 대체 콘텐츠의 유무 하나다. 옛 글이 새 주소로 이사했거나, 그 자리를 대신할 만한 실질적인 후속 콘텐츠가 있으면 301로 그리로 보낸다. 사용자와 검색엔진 모두 이어지는 맥락을 얻으니 이득이다.


반대로 그냥 삭제됐고 대체물이 없으면 410이다. 여기서 흔한 실수가, 대체물도 없으면서 무의미하게 홈이나 게시판 목록으로 301을 남발하는 것이다. 구글은 이런 무관한 리다이렉트를 소프트 404로 재분류해버려서 결국 효과가 없다. 사용자 입장에서도 클릭한 글과 전혀 상관없는 목록으로 튕겨나가면 경험이 나쁘다. 없으면 없다고 410으로 끝내는 게, 어설픈 301보다 훨씬 깨끗하고 정직하며 결과도 좋다.


301과 410을 정할 때 사용자 경험도 함께 생각하면 판단이 쉬워진다. 사용자가 그 옛 링크를 클릭했을 때 이어서 볼 만한 콘텐츠가 있으면 301이 친절하고, 그런 게 없다면 엉뚱한 곳으로 보내는 것보다 없어졌다고 알려주는 410이 정직하다. 검색엔진에 좋은 처리는 대개 사람에게도 좋은 처리다.


삭제와 폐기를 구분하는 것도 도움이 된다. 사용자가 스스로 지운 글, 규정 위반으로 내린 글, 게시판 개편으로 사라진 글은 성격이 다르다. 규정 위반 삭제는 되살릴 일이 없으니 410이 깔끔하고, 임시로 내린 글은 상황에 따라 다르다. 삭제의 사유를 기록해 두면 나중에 이 주소를 301로 살릴지 410으로 끝낼지 판단이 쉬워진다.


5. 사이트맵에서도 반드시 빼라

마지막으로 놓치기 쉬운 것. 죽은 주소를 상태 코드로 정리했어도, 그 URL이 여전히 사이트맵에 남아 있으면 절반은 헛일이다. 사이트맵은 구글에게 여기 꼭 가보라고 안내하는 목록인데, 거기에 죽은 주소를 넣어두면 구글을 불러놓고 정작 문 앞에서 410을 주는 자기모순이 된다.


결국 크롤 예산만 더 샌다. 사이트맵에는 무조건 살아 있고 색인 가능한 URL만 넣어야 한다. 상태 코드 정리와 사이트맵 정리는 언제나 세트로 간다. 이상적으로는 글을 삭제하는 파이프라인 안에, 그 URL을 사이트맵에서 빼는 단계를 아예 자동으로 묶어두는 것이다. 사람이 매번 기억해서 두 곳을 맞추는 방식은 언젠가 반드시 어긋난다. 삭제와 사이트맵 제거를 한 트랜잭션으로 엮어두면 이 종류의 실수가 원천 차단된다. 다음 편은 죽은 주소만큼이나 크롤 예산을 갉아먹는 또 다른 주범, 물음표 쿼리 파라미터가 만드는 중복 URL 문제다.


한 가지 덧붙이면, 죽은 주소를 정리하는 작업은 한 번에 끝나는 게 아니라 운영 루틴이어야 한다. 글은 계속 지워지고 게시판은 계속 개편되니, 죽은 주소도 계속 생긴다. 그래서 나는 한 달에 한 번 정도 404 로그를 훑어 새로 생긴 시체 주소를 410으로 정리하는 걸 정기 점검에 넣어두었다. 청소는 이벤트가 아니라 습관이다.