ToolPal
네트워크 케이블과 서버 인프라

URL 파서 완벽 가이드 — URL 구조 분석 방법

📷 Jordan Harrison / Pexels

URL 파서 완벽 가이드 — URL 구조 분석 방법

URL을 프로토콜, 호스트명, 포트, 경로, 쿼리 매개변수, 해시로 분해하는 방법을 실용적인 예제와 함께 알아봅니다.

D작성: Daniel Park2026년 3월 30일7분 소요

개발하다 보면 URL을 손으로 뜯어보는 상황이 생각보다 자주 온다. 예를 들어 백엔드에서 넘어온 redirect URL에서 특정 파라미터만 뽑아야 할 때, 아니면 GA(Google Analytics)에서 UTM 파라미터가 제대로 붙었는지 확인하고 싶을 때. 이럴 때 URL을 눈으로 파싱하려고 하면 금방 머리가 아파진다.

URL이 복잡해질수록 더 그렇다. 쿼리스트링에 파라미터가 10개 넘어가고, 값에 인코딩된 문자가 섞여 있으면 구분자 어디서 끊기는지도 헷갈리기 시작한다.

이 글에서는 URL의 구조를 차근차근 뜯어보고, 실제 개발 현장에서 어떻게 URL 파싱을 활용하는지 정리해본다.


URL이 뭔지 다시 한번 짚고 가자

URL은 Uniform Resource Locator의 약자다. 말 그대로 "어떤 리소스가 어디에 있는지 알려주는 주소"다. 우리가 매일 브라우저 주소창에 입력하는 그것.

그냥 문자열처럼 보이지만, 사실 URL은 꽤 정교한 규약(RFC 3986)에 따라 구성되어 있다. 각 부분이 명확한 역할을 하고 있고, 이 구조를 모르면 URL 조작이나 디버깅이 어려워진다.


URL의 구성 요소 완전 분해

이런 URL이 있다고 해보자.

https://user:pass@api.example.com:8080/v1/search?q=hello&page=2#results

복잡해 보이지만 쪼개면 다음과 같다.

1. 프로토콜 (Protocol / Scheme)

https:// 부분이다. 이 리소스에 접근할 때 어떤 통신 방식을 쓸지 지정한다. 웹에서는 httphttps가 주류지만, ftp, mailto, ws(WebSocket), file 같은 것도 있다.

httpshttp에 TLS 암호화가 추가된 버전이다. 요즘은 대부분의 사이트가 https를 강제한다.

2. 사용자 정보 (Userinfo)

user:pass@ 부분이다. 인증 정보를 URL에 직접 포함하는 방식인데, 보안상 이유로 현대 웹에서는 거의 쓰지 않는다. 브라우저도 이 부분을 포함한 URL에는 경고를 띄우기도 한다. FTP URL이나 일부 레거시 시스템에서 가끔 볼 수 있다.

3. 호스트명 (Hostname)

api.example.com 부분이다. DNS로 IP 주소로 변환된다. 서브도메인(api), 도메인(example), TLD(.com)로 다시 쪼갤 수 있다.

로컬 개발할 때는 localhost127.0.0.1을 쓰게 된다.

4. 포트 (Port)

:8080 부분이다. 호스트의 어떤 포트로 연결할지 지정한다. 생략하면 프로토콜 기본값이 쓰인다. http는 80, https는 443이 기본이다.

개발 환경에서 Next.js는 3000번, Vite는 5173번을 기본으로 쓰는 식이다.

5. 경로 (Path)

/v1/search 부분이다. 서버에서 어떤 리소스를 요청하는지 나타낸다. REST API에서는 리소스의 계층 구조를 표현하는 데 쓰인다.

6. 쿼리 문자열 (Query String)

?q=hello&page=2 부분이다. ?로 시작하고, 키=값 쌍을 &로 구분한다. 페이지 경로는 그대로 두면서 서버에 추가 정보를 전달하는 용도다. 검색어, 필터, 정렬 기준, 페이지 번호 같은 것들이 여기 들어간다.

값에 특수 문자나 한글이 포함되면 퍼센트 인코딩(%EC%95%88%EB%85%95 같은 형태)으로 변환된다.

7. 해시 (Fragment / Hash)

#results 부분이다. 페이지 내 특정 위치(앵커)로 이동할 때 쓴다. 그런데 여기서 중요한 사실이 하나 있다.

Fragment는 서버로 전달되지 않는다.

이건 꽤 많은 개발자들이 놓치는 포인트다. # 뒤의 내용은 브라우저에서만 처리된다. 서버 사이드에서 fragment 값을 읽을 수 없다는 뜻이다. 서버 로그에서 fragment를 찾아봐도 없는 게 정상이다.

SPA(Single Page Application)에서 해시 라우팅(/#/about 같은 방식)을 쓰는 이유도 여기에 있다. 서버에는 항상 /만 도달하고, 클라이언트에서 # 뒤를 보고 어떤 뷰를 렌더링할지 결정한다.


JavaScript로 URL 파싱하기

브라우저와 Node.js 모두 내장 URL API를 제공한다. 외부 라이브러리 없이 URL을 파싱할 수 있다.

const url = new URL('https://api.example.com:8080/v1/search?q=hello&page=2#results');

console.log(url.protocol);  // "https:"
console.log(url.hostname);  // "api.example.com"
console.log(url.port);      // "8080"
console.log(url.pathname);  // "/v1/search"
console.log(url.search);    // "?q=hello&page=2"
console.log(url.hash);      // "#results"

searchParams를 쓰면 쿼리 파라미터를 개별적으로 다룰 수 있다.

const url = new URL('https://example.com/search?q=hello&page=2&sort=desc');

// 특정 파라미터 값 가져오기
console.log(url.searchParams.get('q'));     // "hello"
console.log(url.searchParams.get('page')); // "2"

// 모든 파라미터 순회
url.searchParams.forEach((value, key) => {
  console.log(`${key}: ${value}`);
});
// q: hello
// page: 2
// sort: desc

// 파라미터 추가/수정
url.searchParams.set('page', '3');
url.searchParams.append('filter', 'new');

console.log(url.toString());
// "https://example.com/search?q=hello&page=3&sort=desc&filter=new"

옛날에는 이런 거 하려면 location.search를 직접 문자열로 쪼개거나, 정규식을 썼는데 요즘은 URL API가 워낙 잘 되어 있어서 그럴 필요가 없다.


실용적인 사용 사례

API 디버깅

REST API를 호출했는데 서버에서 예상과 다른 응답이 돌아올 때, 요청 URL이 제대로 구성됐는지 확인하는 게 첫 번째 디버깅 단계다. URL 파서를 쓰면 쿼리 파라미터가 어떻게 인코딩됐는지, 경로가 올바른지 한눈에 볼 수 있다.

특히 API 클라이언트 라이브러리를 쓸 때 내부적으로 URL을 어떻게 조립하는지 확인하고 싶을 때 유용하다. axiosfetch에 전달한 파라미터가 실제로 URL에 어떻게 붙는지 검증할 수 있다.

UTM 파라미터 추출

마케팅에서 UTM 파라미터는 트래픽 소스를 추적하는 데 쓰인다. 이런 URL이 있다고 하자.

https://example.com/landing?utm_source=newsletter&utm_medium=email&utm_campaign=spring2026&utm_content=cta-button

GA나 다른 분석 도구가 이 파라미터를 자동으로 수집해주지만, 가끔 직접 파싱해서 처리해야 할 때가 있다. 예를 들어 특정 캠페인에서 온 사용자에게 다른 UI를 보여준다거나, 서버 사이드에서 UTM 데이터를 DB에 저장해야 할 때.

function extractUtmParams(urlString) {
  const url = new URL(urlString);
  const utmKeys = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content'];

  return utmKeys.reduce((acc, key) => {
    const value = url.searchParams.get(key);
    if (value) acc[key] = value;
    return acc;
  }, {});
}

const result = extractUtmParams('https://example.com/landing?utm_source=newsletter&utm_medium=email&utm_campaign=spring2026');
console.log(result);
// { utm_source: 'newsletter', utm_medium: 'email', utm_campaign: 'spring2026' }

리다이렉트 URL 분석

OAuth 인증 플로우나 소셜 로그인을 구현할 때, 콜백 URL에 authorization code나 에러 정보가 쿼리 파라미터로 붙어 온다. 이걸 파싱해서 다음 단계를 처리해야 한다.

// OAuth 콜백 처리 예시
const callbackUrl = 'https://myapp.com/auth/callback?code=abc123&state=xyz789';
const url = new URL(callbackUrl);

const code = url.searchParams.get('code');
const state = url.searchParams.get('state');
const error = url.searchParams.get('error');

if (error) {
  console.error('인증 실패:', error);
} else {
  console.log('인증 코드:', code);
  // 서버에 code 전달하여 access token 교환
}

상대 경로를 절대 경로로 변환

URL 생성자의 두 번째 인수로 base URL을 넣으면 상대 경로를 절대 경로로 변환할 수 있다.

const base = 'https://example.com/blog/post/123';
const relative = '../images/thumbnail.jpg';

const absolute = new URL(relative, base);
console.log(absolute.href);
// "https://example.com/blog/images/thumbnail.jpg"

URL 인코딩과 디코딩

URL에서 사용할 수 없는 문자(한글, 공백, 특수 문자 등)는 퍼센트 인코딩으로 변환해야 한다. JavaScript에는 이를 위한 함수가 여러 개 있는데, 뭘 써야 할지 헷갈리는 경우가 많다.

// encodeURIComponent — 쿼리 파라미터 값 인코딩에 적합
console.log(encodeURIComponent('안녕 world!'));
// "%EC%95%88%EB%85%95%20world!"

// decodeURIComponent — 위의 반대
console.log(decodeURIComponent('%EC%95%88%EB%85%95%20world!'));
// "안녕 world!"

// encodeURI — URL 전체 인코딩 (/ : ? 같은 구조 문자는 건드리지 않음)
console.log(encodeURI('https://example.com/search?q=안녕'));
// "https://example.com/search?q=%EC%95%88%EB%85%95"

실무에서 주의할 점은 encodeURIComponentencodeURI의 차이다. 쿼리 파라미터 값을 인코딩할 때는 encodeURIComponent를 써야 &= 같은 구분자도 인코딩된다. encodeURI는 URL 전체를 인코딩할 때 쓰는데, 구조 문자는 그대로 두기 때문에 파라미터 값에 쓰면 예상치 못한 결과가 나올 수 있다.


URL 파서 도구 활용하기

코드로 파싱하는 게 항상 편한 건 아니다. 개발 중에 특정 URL이 어떻게 구성되어 있는지 빠르게 확인하고 싶을 때, 매번 콘솔에 코드 치는 것도 번거롭다.

toolboxhubs.com/ko/tools/url-parser에서 URL을 입력하면 각 구성 요소를 시각적으로 분리해서 보여준다. 쿼리 파라미터가 키-값 쌍으로 정리되고, 인코딩된 값도 디코딩해서 보여주기 때문에 복잡한 URL을 분석할 때 특히 편하다.

서버로 데이터가 전송되지 않고 브라우저 내장 URL API를 사용해서 처리되기 때문에, API 키나 토큰이 포함된 URL도 안심하고 붙여넣을 수 있다.


흔한 실수와 주의사항

1. Fragment는 서버로 안 간다

앞에서도 언급했지만, 이건 정말 중요하다. 서버 사이드 렌더링이나 API에서 # 뒤의 값을 읽으려는 시도는 절대 작동하지 않는다. Fragment 처리는 무조건 클라이언트 사이드에서 해야 한다.

2. 상대 URL에 new URL()을 직접 쓰면 에러 난다

// 이건 에러
const url = new URL('/search?q=hello');

// 이렇게 써야 한다
const url = new URL('/search?q=hello', 'https://example.com');
// 또는 window.location.origin을 base로 쓰기
const url = new URL('/search?q=hello', window.location.origin);

3. 쿼리 파라미터에 같은 키가 여러 번 나올 수 있다

?color=red&color=blue&color=green 같은 URL은 완전히 유효하다. 이 경우 searchParams.get('color')는 첫 번째 값만 반환한다. 모든 값을 가져오려면 searchParams.getAll('color')를 써야 한다.

const url = new URL('https://example.com?color=red&color=blue&color=green');
console.log(url.searchParams.get('color'));     // "red"
console.log(url.searchParams.getAll('color'));  // ["red", "blue", "green"]

4. URL 비교는 직접 문자열로 하면 안 된다

https://example.comhttps://example.com/은 다른 문자열이지만 사실상 같은 URL이다. 비교할 때는 URL 객체로 파싱한 뒤 origin, pathname 같은 속성을 비교하는 게 안전하다.

5. 포트가 기본값인 경우 생략된다

https://example.com:443/path를 파싱하면 url.port는 빈 문자열을 반환한다. 기본 포트(http는 80, https는 443)는 명시적으로 지정해도 URL 파서가 생략하기 때문이다.


Node.js에서의 URL 파싱

Node.js에서도 동일한 URL 클래스를 사용할 수 있다. 예전에 많이 쓰던 require('url').parse()는 레거시 API라서 새 코드에서는 쓰지 않는 게 좋다.

// Node.js — 최신 방식
const { URL } = require('url'); // Node 10 이전에는 필요, 이후는 전역으로 사용 가능

const url = new URL('https://api.example.com/users?role=admin&page=1');
console.log(url.hostname);               // "api.example.com"
console.log(url.searchParams.get('role')); // "admin"

Express.js에서 요청 URL을 파싱할 때는 req.query가 이미 파싱된 파라미터를 제공하지만, 전체 URL 구조를 보고 싶을 때는 new URL(req.url, 'http://localhost')처럼 쓸 수 있다.


정리

URL은 단순한 문자열이 아니다. 프로토콜, 인증 정보, 호스트, 포트, 경로, 쿼리, 해시라는 7가지 구성 요소가 각자의 역할을 맡고 있는 구조화된 데이터다.

개발 중 URL 관련 버그는 대부분 이 구조를 제대로 이해하지 못하거나, 인코딩 처리를 빠뜨릴 때 생긴다. 특히 fragment는 서버로 전달되지 않는다는 점, 같은 키의 파라미터가 여러 개일 수 있다는 점을 기억해두면 디버깅 시간을 많이 아낄 수 있다.

JavaScript의 내장 URL API는 생각보다 강력하다. 외부 라이브러리 없이도 URL 파싱, 조작, 인코딩 처리를 깔끔하게 할 수 있으니 적극 활용하자.

자주 묻는 질문

D

작성자

Daniel Park

서울에서 활동하는 시니어 프런트엔드 엔지니어. 국내 SaaS 회사들에서 7년간 웹 애플리케이션을 개발하며 개발자 도구, 웹 성능 최적화, 프라이버시 중심 설계에 집중해 왔습니다. JavaScript 생태계 오픈소스 기여자이자 ToolPal 창립자입니다.

더 알아보기

이 글 공유하기

XLinkedIn

관련 글