삼형제 아빠 부업 노트
45분지출 ₩0수입 아직 없음

슬래시 하나가 123개를 어긋나게 했다 — trailingSlash와 308 리디렉션

검색엔진 최적화(SEO)를 위해 야심 차게 생성한 sitemap.xml 파일을 구글 서치콘솔과 네이버 서치어드바이저에 제출했습니다. 사이트 전체의 주요 페이지 주소 123개가 차곡차곡 담겨 있었습니다. 며칠 뒤 색인 결과를 확인하러 서치콘솔에 들어갔는데, '리디렉션이 있는 페이지' 경고가 대량으로 발생하며 색인이 지연되고 있었습니다.

사이트맵에 등록된 URL 중 하나를 curl 명령어로 터미널에서 직접 호출해 보았습니다. 정상적인 200 OK 응답 대신 '308 Permanent Redirect'가 돌아왔습니다. 다음 주소도, 그다음 주소도 마찬가지였습니다. 사이트맵에 실린 123개 URL 전체가 리디렉션 응답을 반환하고 있었습니다.

원인은 Next.js 설정(next.config.ts)의 'trailingSlash: true' 옵션에 있었습니다. 이 옵션을 켜면 Next.js 정적 내보내기는 모든 페이지를 디렉터리 기반의 'index.html' 구조로 빌드하기 때문에, 실제 정규 주소는 반드시 끝에 슬래시가 붙는 '/ko/' 형태가 됩니다. 슬래시 없이 '/ko'로 접근하면 서버는 슬래시가 붙은 정규 주소로 브라우저를 영구 리디렉션시킵니다.

일반 사용자가 크롬이나 사파리 같은 브라우저로 접속할 때는 브라우저가 알아서 308 리디렉션을 따라가므로 겉보기에 아무런 문제가 느껴지지 않았습니다. 또한 HTML 헤더에 정의된 canonical 태그와 hreflang 메타태그도 이미 슬래시로 끝나도록 작성되어 있었습니다. 오직 사이트맵을 생성하는 코드(app/sitemap.ts) 한 곳에서만 끝의 슬래시 처리를 빠뜨리고 있었던 것입니다.

사람에게는 슬래시 하나의 차이가 아무것도 아니지만, 크롤링 예산(Crawl Budget)을 엄격하게 관리하는 검색엔진 로봇에게는 치명적입니다. 사이트맵에 적힌 모든 페이지마다 불필요한 HTTP 리디렉션 홉(Hop)이 발생하면 크롤러는 사이트의 구조적 신뢰도를 낮추고 신규 콘텐츠의 색인 우선순위를 뒤로 미룹니다.

코드를 고치는 것은 단 한 줄이면 충분했습니다. URL 생성 유틸리티 함수에 명시적인 슬래시를 붙여주는 것이었습니다. 하지만 빌드 결과물의 HTML 파일들과 sitemap.xml의 주소가 100% 일치하는지 자동으로 검증하는 회귀 테스트 스크립트(scripts/check-index.mjs)를 구축해 두지 않으면 언제든 재발할 수 있는 문제였습니다. 보이지 않는 작은 규격 하나가 전체 시스템의 신뢰도를 좌우한다는 값진 교훈을 얻었습니다.