다른 문서에서 image.png를 복사했는데 내 페이지에서는 그림이 열리지 않습니다. ../download/file.csv를 붙였더니 예상과 다른 폴더로 갑니다. 상대 주소에는 출발점이 숨어 있습니다. 주소 문자열만 떼어 놓으면 같은 글자도 다른 최종 URL이 될 수 있습니다. 이번 실험은 Node.js의 URL 구현으로 여섯 참조를 해석하고 끝의 슬래시와 문서 base가 결과를 어떻게 바꾸는지 직접 확인합니다.
주소를 복사할 때는 원문과 출발 주소를 한 쌍으로 남깁니다.
완전한 URL에는 스킴과 호스트, 경로 등이 들어 있지만 상대 참조는 일부를 기준 주소에서 가져옵니다. 같은 image.png라도 기준이 /docs/guide/인지 /docs/인지에 따라 도착 위치가 다릅니다. 문서에서 보이는 링크 글자와 실제 href도 다를 수 있으므로 표시 문구만 복사해서는 목적지를 알 수 없습니다.
검수표에는 원래 href, 원래 문서 URL, 문서의 base URI, 해석한 절대 URL을 따로 적습니다. 최종 주소를 저장했다고 원래 상대 참조를 지우지는 않습니다. 페이지를 다른 폴더나 도메인으로 옮긴 뒤 어떤 규칙 때문에 주소가 달라졌는지 추적하려면 출발값이 필요합니다. 실제 로그인 주소나 인증 쿼리는 이 표에 넣기 전에 공개 범위를 확인해야 합니다.
기준 주소를 디렉터리로 볼지 파일로 볼지 결과가 다릅니다.
이번 기준은 https://example.test/docs/guide/입니다. 마지막 슬래시가 있으므로 image.png는 guide 폴더 아래에 놓입니다. 반면 끝에 슬래시가 없는 https://example.test/docs/guide를 기준으로 하면 마지막 guide 부분을 대체해 /docs/image.png가 됩니다. 문자열의 마지막에 경로를 붙이는 동작과는 다릅니다.
실제 서버가 두 주소를 리디렉션해 같은 페이지로 보여 주더라도 문서가 받은 최종 URL을 기준으로 링크가 해석될 수 있습니다. 주소창의 초기 입력과 최종 주소를 구분하세요. 서버의 리디렉션 정책과 URL 해석 규칙이 함께 작용하는 문제라 “브라우저가 알아서 보정한다”는 설명으로 끝내면 다른 환경에서 다시 깨질 수 있습니다.
여섯 참조가 서로 다른 부분을 바꿉니다.
image.png는 현재 디렉터리에 파일명을 붙이고, ../download/file.csv는 한 단계 위 docs 아래의 download로 이동합니다. /contact는 경로를 사이트 루트에서 다시 시작합니다. ?lang=ja는 경로는 유지하고 쿼리를 바꿉니다. #downloads는 같은 문서의 프래그먼트를 지정합니다. //cdn.example.test/a.png는 현재 스킴을 사용하면서 호스트를 바꿉니다.
코드는 이 여섯 값을 new URL(ref, base)에 넣어 href를 기록했습니다. ../download/file.csv의 결과는 https://example.test/docs/download/file.csv였습니다. 이 경로가 실제로 존재하는지는 확인하지 않았습니다. URL이 해석됐다는 사실과 HTTP 응답이 성공했다는 사실은 별도이며, 파일을 실행하거나 다운로드하지도 않았습니다.
base 요소가 있으면 주소창의 경로만 봐서는 부족합니다.
문서에 base href가 있으면 상대 URL의 기준이 달라질 수 있습니다. 합성 예에서는 기준을 https://static.example.test/assets/로 선언하고 image.png를 해석해 같은 호스트의 assets 아래 주소를 얻었습니다. 현재 페이지가 example.test/docs/guide/에 보이더라도 선언된 기준이 적용되는 문맥이라면 도착지가 static.example.test가 됩니다.
HTML에서는 href를 가진 첫 base 요소의 역할과 브라우저의 문서 기준을 확인해야 합니다. 실제 진단에는 document.baseURI를 보고 링크 요소의 원래 href 속성과 해석된 href 속성을 나란히 읽는 방법이 있습니다. 이번 코드는 브라우저 DOM을 실행한 것이 아니라 선언된 base 문자열을 URL 생성자에 넣은 실험입니다. DOM 배치나 잘못된 HTML을 시험했다고 쓰지 않습니다.
입력별 결과를 편집 판단에 연결하기
다음 표의 결과는 제공 실행 파일에서 확인한 값이며, 해석과 다음 확인은 그 결과를 이용하는 편집 예시입니다. 추가 시험을 수행한 결과로 세지 않습니다.
| 입력·관찰 | 실행 결과 또는 선택 | 판단 근거 | 다음 확인 |
|---|---|---|---|
| image.png | 현재 디렉터리에 붙는 파일 참조입니다. | 슬래시가 있는 기준에서는 /docs/guide/image.png가 됩니다. 파일 이름만 같다고 다른 문서에 붙여 넣어도 같은 이미지를 가리키는 것은 아닙니다. | 원래 문서의 base URI를 함께 인계하고 이미지 파일 존재 여부는 별도로 확인합니다. |
| ../download/file.csv | 부모 경로를 거친 참조입니다. | 기준의 guide를 벗어나 /docs/download/file.csv로 갑니다. 상위 이동 표기를 삭제하거나 문자열만 이어붙이면 다른 위치를 만들 수 있습니다. | 계산한 절대 주소를 먼저 확인한 다음 공개 파일의 제목·형식·접근 조건을 대조합니다. |
| /contact | 사이트 루트부터 시작하는 참조입니다. | 현재 /docs/guide/ 경로와 관계없이 같은 호스트의 /contact가 됩니다. 하위 폴더에 사이트를 설치한 환경에서는 기대가 다를 수 있습니다. | 배포 위치와 애플리케이션 경로 정책을 확인하고 로컬·운영의 차이를 기록합니다. |
| ?lang=ja / #downloads | 경로를 유지하며 쿼리 또는 프래그먼트를 바꿉니다. | 언어 쿼리가 실제로 번역 페이지를 제공하는지는 이 코드가 알 수 없습니다. 프래그먼트 대상 id의 존재도 별도입니다. | 해석 성공과 페이지 기능 성공을 다른 검사 항목으로 남깁니다. |
| //cdn.example.test/a.png | 스킴을 물려받고 호스트를 바꿉니다. | 같은 사이트의 상대 경로처럼 보여도 다른 호스트를 가리킵니다. 호스트의 신뢰·권한·자산 정책을 이 문자열 해석으로 보장할 수 없습니다. | 외부 자산으로 분류하고 사용 권한과 운영 환경의 허용 정책을 확인합니다. |
페이지 안의 #링크도 base의 영향을 받을 수 있습니다.
#downloads는 언제나 현재 화면 안에서만 이동한다고 생각하기 쉽습니다. 그러나 다른 문서를 가리키는 base가 적용되면 그 기준 문서의 #downloads가 됩니다. 문서의 목차를 다른 곳에 붙여 넣었을 때 새 요청이 일어나거나 원하지 않는 페이지로 가는 원인이 될 수 있습니다. 링크가 짧다는 이유로 내부 이동이라고 단정하지 않습니다.
또한 최종 URL에 #downloads가 있어도 대상 HTML에 같은 id가 존재하는지는 별도 검사입니다. 서버 응답 200으로 프래그먼트 도착까지 증명할 수 없습니다. 템플릿을 옮긴 뒤 목차 링크를 한 번씩 클릭하고, 실제 스크롤 위치와 키보드 탐색을 확인하세요. 중복 id가 있으면 다른 위치로 이동할 수 있으므로 주소 해석 이후의 문서 구조도 봐야 합니다.
파일 주소와 운영 동작 주소를 같은 방식으로 열지 않습니다.
주소 해석 자체는 네트워크 요청이 필요 없으므로 링크를 먼저 분류하는 데 사용할 수 있습니다. 해석 결과가 다른 호스트로 나갔는지, https인지, mailto나 javascript 같은 다른 스킴인지 확인한 뒤 다음 단계를 정합니다. URL 생성자가 문자열을 받아들였다는 이유로 그 주소를 자동으로 실행하면 안 됩니다.
특히 관리자 작업·로그아웃·삭제·결제 링크와 인증 토큰이 있는 주소는 읽기 전용 링크 검사에서 제외하거나 별도 승인을 받아야 합니다. 이번 예제는 예약된 example.test 주소만 사용하므로 실제 외부 서비스를 호출하지 않습니다. 주소를 해석하는 규칙을 설명한 것이며 모든 입력을 안전하다고 허용하는 화이트리스트나 보안 검사기는 아닙니다.
페이지를 이사할 때는 링크의 의도를 먼저 선택합니다.
상대 경로는 같은 묶음을 폴더째 옮길 때 편리할 수 있고 절대 주소는 다른 문서에 인용할 때 원래 위치를 유지하기 쉽습니다. 어느 쪽이 언제나 좋다고 정할 수는 없습니다. 로컬 미리보기·운영 도메인·다국어 경로·CDN처럼 배포 조건을 적고, 이동 뒤에도 따라가야 할 링크와 원래 사이트를 가리켜야 할 링크를 나눕니다.
한글 경로와 퍼센트 인코딩이 들어 있는 주소는 화면 표시와 원시 문자열이 달라질 수 있습니다. 최종 URL을 보기 좋게 만들겠다고 디코딩한 뒤 다시 임의로 조합하면 구분자가 바뀔 수 있습니다. 주소 도구의 파싱 결과를 사용하고 원본도 남기는 편이 안전합니다. 이번 여섯 예제에는 복잡한 인코딩이나 사용자 정보가 들어 있지 않아 그 조건의 동작은 시험하지 않았습니다.
출발점 누락을 고치는 실무 순서
먼저 문제가 난 링크의 getAttribute 값과 실제 문서 최종 주소를 기록합니다. 다음으로 baseURI와 해석된 href를 비교합니다. 그다음 같은 경로를 슬래시가 있는 기준과 없는 기준으로 각각 해석해 예상이 어디서 갈렸는지 봅니다. 마지막에 공개 브라우저로 최종 주소와 본문을 확인합니다. 이 순서면 URL 구성 문제와 서버의 없는 파일 문제를 구분하기 쉽습니다.
메뉴와 본문 링크가 다른 템플릿에서 생성되는 경우에는 둘을 따로 확인합니다. 메인 메뉴는 루트 경로인데 본문은 파일명만 쓸 수 있습니다. 일부 링크가 정상이라는 이유로 전체를 통과시키지 마세요. 주소가 다른 곳으로 갔다면 링크 글자만 수정하지 말고 생성한 템플릿이나 문서의 기준 설정을 고쳐 같은 문제가 다시 생기지 않게 합니다.
재현 파일의 결과와 확인하지 않은 범위를 함께 읽습니다.
제공 Python 파일은 설치된 Node.js를 호출해 실제 URL 생성자의 결과를 수집합니다. Python과 Node.js가 모두 필요하고 외부 패키지는 사용하지 않습니다. trailingSlash 배열 두 값과 여섯 rows, 별도 baseElementExample이 결과입니다. 기대한 주소를 assert로 대조했으며 networkRequests는 0입니다.
브라우저별 렌더링, 페이지의 base 요소 배치, HTTP 리디렉션, 파일 접근 권한, 링크의 콘텐츠 적합성은 별도입니다. 같은 문자열을 표준 구현으로 해석했다는 작은 사실을 전체 링크가 정상이라는 결론으로 키우지 않습니다. 주소 점검의 첫 결과는 “열림”이 아니라 “어떤 출발점에서 어느 목적지가 계산되었는가”여야 합니다.
주소를 직접 입력할 때와 요소의 href 속성을 읽을 때도 결과 표현이 다를 수 있습니다. 원시 속성에는 image.png만 있지만 브라우저가 해석한 속성에는 전체 주소가 보일 수 있습니다. 이를 두 개의 서로 다른 링크라고 세지 말고 원문과 해석 결과로 연결합니다. 링크 목록을 복사할 도구를 만들 때 이 두 값을 같이 출력하면 편집자가 어디서 출발점을 잃었는지 찾기 쉽습니다.
이 글의 입력·실행 자료
합성 자료의 실제 검증일은 2026-10-15입니다. 글의 예약 발행일과 구분합니다. 코드의 fixtureCreatedOn은 예제 작성일이고 executedAt은 재실행할 때의 실제 UTC 시각입니다. Python 3 표준 라이브러리와 설치된 Node.js를 사용합니다. 실제 고객정보나 새 Gemini 응답을 입력하지 않았습니다.
python3 relative-url-base-resolution-lab.py확인한 공식 원문
2026-10-15에 열어 확인했습니다. 제공 예제의 규칙과 공식 문서의 일반 기능 설명을 구분해 읽습니다.



