서브도메인 링크가 404가 될 때: 상대 URL과 기준 주소 점검하기
루트 상대 링크가 잘못된 서브도메인으로 향하는 원인을 설명한다. Node.js 15개 테스트로 URL 해석과 링크 생성기를 검증하고, 기존 콘텐츠 수정·배포 검사·404와 색인 설정의 역할을 구분한다.
같은 경로라도 기준 주소가 다르면 다른 서비스로 향한다. AI 생성 에디토리얼 이미지.
앱과 블로그를 서로 다른 서브도메인에 배포하면, 개발 중에는 멀쩡했던 링크가 운영에서 404로 이어지는 일이 있다. 화면에 보이는 링크 문구와 경로는 맞는데 요청 호스트가 틀린 경우다.
예를 들어 블로그는 blog.example.com, 실제 기능은 app.example.com에서 제공한다고 하자. 블로그 본문에 /module/parser를 넣었다고 해서 브라우저가 앱의 위치를 알아서 찾지는 않는다. 어떤 주소를 기준으로 링크를 해석하는지부터 확인해야 한다.
아래에서는 문서용 예시 도메인만 사용한다. 실제 서비스의 장애나 색인 상태를 진단한 글이 아니라, URL 해석 규칙과 링크 생성 코드를 재현하는 실습이다.
1. 맨 앞의 /는 다른 서비스로 가라는 뜻이 아니다
기준 주소가 https://blog.example.com/posts/url-guide/일 때 결과는 다음과 같다.
/module/parser→https://blog.example.com/module/parser
module/parser→https://blog.example.com/posts/url-guide/module/parser
https://app.example.com/module/parser→ 그대로 앱 주소
HTTP(S)에서 단일 /로 시작하는 루트 상대 경로는 기준 주소의 scheme·host·port를 유지하고 경로를 루트부터 해석한다. 여기서 루트는 등록 도메인인 example.com이나 개발팀이 생각하는 ‘메인 서비스’가 아니다. blog.example.com을 기준으로 해석하면 여전히 블로그 호스트다. WHATWG URL 해석 규칙
반대로 앞에 /가 없는 경로는 기준 URL의 디렉터리에 이어진다. 기준 주소의 마지막 슬래시도 중요하다. https://blog.example.com/posts/url-guide처럼 끝에 /가 없으면 module/parser의 결과는 https://blog.example.com/posts/module/parser가 된다. URL 해석을 문자열 이어 붙이기로 구현하면 이런 차이를 놓치기 쉽다. WHATWG relative state
2. 원문 href와 해석된 목적지를 함께 본다
장애를 조사할 때는 링크 문구보다 다음 세 가지를 먼저 기록한다.
- HTML에 들어 있는
href원문
- 브라우저가 계산한 절대 주소
- 계산에 사용한
document.baseURI
브라우저 개발자 도구 콘솔에서 특정 형태의 링크를 읽기 전용으로 확인하는 예시다.
이 코드는 /module/로 시작하는 원문만 좁혀 본다. 모든 링크를 검사하는 크롤러나 배포 검증기는 아니다. 같은 페이지에서 본문 링크는 상대 경로이고 하단 버튼은 절대 주소라면, 둘이 서로 다른 호스트로 향할 수 있다. 공통 컴포넌트만 확인하고 본문 렌더러를 놓치지 않아야 한다.
문서에 base 요소가 있으면 주소창만 봐서는 기준을 잘못 판단할 수 있다. base href는 문서의 URL 해석 기준을 바꾸므로 다른 상대 링크·리소스·fragment 링크에도 영향을 줄 수 있다. 링크 하나를 고치려고 문서 전체의 기준을 바꾸기보다는, 먼저 영향 범위를 확인하는 편이 안전하다. HTML Standard의 base 요소
3. 서비스 경계를 넘는 링크는 생성 함수를 둔다
본문·버튼·메일·사이트맵이 각자 URL 문자열을 만들면 같은 콘텐츠의 목적지가 달라지기 쉽다. 앱 내부 이동에는 상대 경로를 사용하더라도, 다른 서비스로 보내는 링크는 목적지 origin을 명시하는 작은 함수를 두면 규칙을 모으기 좋다.
다음은 모듈 ID를 영문 소문자·숫자·하이픈으로 제한한 예시다. 임의 URL을 입력받는 범용 정제 함수가 아니다.
links.mjs
블로그 렌더러와 하단 버튼에서 모두 moduleUrl('first-follow')를 사용하면 같은 앱 주소를 얻는다. 실제 프로젝트에서는 APP_ORIGIN을 신뢰할 수 있는 배포 설정에서 관리하고, staging과 production이 서로 뒤섞이지 않는지도 검사해야 한다. 예제는 앱 기능이 /module/에 있다는 전제를 코드로 고정했다.
여기서 new URL(input, base)의 base가 보안상 허용 목록이 된다고 생각하면 안 된다. input이 절대 URL이면 base와 관계없이 그 주소가 선택되고, //other.example/path도 호스트를 바꿀 수 있다. Node 공식 문서는 입력이 절대 URL인지 상대 URL인지 확실하지 않다면 결과의 origin을 확인하도록 안내한다. 위 함수는 입력을 URL이 아닌 제한된 slug로 받는 방식으로 범위를 좁힌다. Node.js URL 생성자
4. URL 해석과 입력 제한을 로컬에서 검증한다
다음 파일을 links.mjs 옆에 두면 네트워크 요청 없이 실행할 수 있다. 앞부분은 URL 해석 규칙을, 뒷부분은 링크 생성기의 입력 계약을 검사한다.
links.test.mjs
실행 명령:
Node.js v24.22.1에서 15개 테스트가 모두 통과했다. 구성은 URL 해석 6개, 정상 링크 생성 1개, 잘못된 slug 거부 8개다. 어떤 예시 도메인에도 요청을 보내지 않았다.
이 결과가 보장하는 범위는 문자열이 예상 URL로 해석되고 함수가 정한 입력을 제한한다는 것까지다. 해당 경로가 실제로 존재하는지, 로그인이 필요한지, CDN의 리디렉션이 맞는지, 페이지가 원하는 내용을 보여 주는지는 배포 환경에서 별도로 확인해야 한다.
5. 기존 글 수정과 생성 경로 수정을 함께 한다
오류가 보이는 글 하나를 고치는 것으로 끝내면 다음 배포에서 다시 생길 수 있다. 다음 순서로 정리할 수 있다.
- 생성 원점 확인: Markdown 템플릿, CMS 필드, AI 콘텐츠 생성 프롬프트, 링크 변환기 중 어디서 주소가 만들어지는지 찾는다. AI가 작성한 본문도 렌더링된 링크를 별도로 검사한다.
- 기존 콘텐츠 조사: 문제 경로를 가진 링크를 모으고, 각 링크가 실제로 앱 기능을 가리키는지 확인한다. 코드 예제에 등장하는 문자열까지 무조건 치환하지 않는다.
- 렌더 결과 검사: 원본 Markdown에 올바른 주소가 있어도 변환기가 바꿀 수 있다. 최종 HTML의
href와 브라우저에서 해석된 주소를 확인한다.
- 배포 후 검증: 대표 링크의 최초 요청 URL, 리디렉션 경로, 최종 상태 코드와 실제 페이지 내용을 확인한다. 로그인 화면의 200 응답만 보고 정상 콘텐츠라고 단정하지 않는다.
Google은 일반적으로 href가 있는 a 요소의 링크를 추출할 수 있다고 설명한다. 자바스크립트 클릭 이벤트만으로 목적지를 옮기는 방식으로 문제를 감추기보다, HTML의 링크 주소 자체를 올바르게 제공하는 편이 사용자와 크롤러가 같은 목적지를 보게 한다. 상대 링크도 정상적인 방식이며, 서비스 경계와 기준 주소가 맞는지가 관건이다. Google 링크 권장사항
CI에도 두 층의 검사를 두면 좋다. 링크 생성 함수의 단위 테스트와, 생성된 HTML에서 앱 기능 링크가 허용한 origin으로 향하는지 확인하는 검사는 서로 다른 실수를 찾는다. 전자는 함수 규칙을 지키고 후자는 함수 사용을 빠뜨린 본문·템플릿을 찾는 데 도움이 된다.
6. 404·noindex·canonical을 한꺼번에 고치지 않는다
색인 보고서에서 제외 URL이 보이면 먼저 원인을 분리해야 한다. 잘못된 호스트로 향하는 본문 링크와 의도된 검색 제외는 같은 문제가 아니다.
- 404/410: 실제로 존재하지 않는 콘텐츠라면 적절한 응답일 수 있다. 오류 화면을 200으로 반환하면 Google이 soft 404로 판단할 수도 있다. HTTP 상태 코드 처리
- noindex: 검색 결과에 포함하지 않도록 하는 지시다. 깨진 링크의 목적지나 서버 라우팅을 수정하지 않는다. Google이 지시를 읽으려면 해당 페이지를 크롤링할 수 있어야 한다. noindex 안내
- canonical: 중복되거나 매우 유사한 페이지의 대표 URL을 알려 주는 신호다. 사용자가 클릭한 링크를 다른 주소로 이동시키는 기능이 아니다. canonical 안내
이미 퍼진 잘못된 주소에 의미상 대응되는 정상 목적지가 있다면 리디렉션을 함께 설계할 수 있다. 다만 없는 경로를 전부 홈페이지로 보내는 방식은 사용자를 혼란스럽게 하고 soft 404로 처리될 수 있다. Google도 관련 없는 목적지로 일괄 이동시키는 방식을 피하도록 안내한다. URL 변경과 리디렉션 권고
우선순위는 잘못된 링크의 생성 원인을 고치고, 기존 링크와 배포 결과를 확인하는 것이다. 색인 설정은 해당 페이지의 목적에 맞게 따로 판단한다. 경로 문자열이 맞아 보여도 목적지 호스트까지 맞는지 확인하는 습관이 여러 서브도메인을 운영할 때 작은 404를 반복하지 않는 출발점이다.