Menu

JavaScript URL과 URLSearchParams로 쿼리스트링 다루기

정규식 없이, 인코딩 버그 없이 URL을 파싱하고 조립하는 방법. URL 객체와 URLSearchParams API를 제대로 활용해 봅시다.

URL을 문자열로 쪼개지 마세요

URL API가 생기기 전에는 다들 split('?')에 정규식 몇 줄, 그리고 기도로 URL을 파싱했습니다. 값에 &, =, 공백, 비 ASCII 문자가 들어오기 전까지는 그럭저럭 돌아갔죠. 하지만 그런 값이 섞이는 순간 바로 깨집니다. 브라우저와 Node 모두 제대로 된 파서를 기본으로 제공하니, 이걸 쓰세요.

index.js
Output
Click Run to see the output here.

한 번의 호출만으로 URL의 모든 구성 요소가 이미 분리되어 있고 디코딩까지 깔끔하게 끝나 있습니다. 잘못된 입력이 들어오면 생성자가 TypeError를 던지는데, 보통은 이 동작이 오히려 반갑습니다 — 쓰레기 같은 URL은 조용히 이상한 값을 뒤로 흘려보내기보다 그 자리에서 요란하게 터지는 편이 낫죠.

쿼리스트링 파싱하기 (URLSearchParams 사용법)

모든 URL 객체에는 .searchParams 프로퍼티가 있습니다. 이건 쿼리 문자열을 읽고 쓸 줄 아는 URLSearchParams 객체예요:

index.js
Output
Click Run to see the output here.

짚고 넘어갈 만한 포인트 몇 가지:

  • 값은 이미 디코딩된 상태로 반환됩니다. ?name=Ada%20Lovelace를 넘기면 "Ada Lovelace"가 나와요.
  • 모든 값은 문자열입니다. "2"2가 아니에요. 숫자로 써야 한다면 Number()로 변환하세요.
  • 키가 중복되어도 괜찮습니다. get은 첫 번째 값만 돌려주고, 전부 필요하면 getAll을 쓰면 됩니다.
  • 없는 키는 undefined가 아니라 null을 돌려줍니다. 그래서 ?? "default" 패턴과 궁합이 잘 맞아요.

쿼리스트링 만들기

URLSearchParams를 쓰면 쿼리스트링을 직접 조립할 수 있습니다. 이스케이프를 손으로 할 필요도, &로 일일이 이어 붙일 필요도 없어요:

index.js
Output
Click Run to see the output here.

객체로부터 바로 만들 수도 있는데, [key, value] 쌍으로 된 이터러블이든 평범한 객체든 모두 받아줍니다:

index.js
Output
Click Run to see the output here.

set vs append의 차이: set은 해당 키에 이미 값이 있으면 덮어쓰고, append는 같은 키에 값을 하나 더 붙입니다. 태그나 필터처럼 키가 여러 번 반복될 수 있는 경우엔 append를, 값이 하나뿐인 파라미터엔 set을 쓰면 됩니다.

URL 수정하기

URL은 살아있는 객체이기 때문에, searchParams를 바꾸면 .search.href가 자동으로 같이 업데이트됩니다:

index.js
Output
Click Run to see the output here.

기존 URL에 쿼리 파라미터를 덧붙일 때 가장 깔끔한 방법이 바로 이 방식입니다. URL에 이미 ?가 붙어 있는지 따로 확인할 필요도 없고, &를 붙여야 할지 ?를 붙여야 할지 고민할 필요도 없습니다.

URL의 다른 부분도 같은 방식으로 수정할 수 있습니다:

index.js
Output
Click Run to see the output here.

파라미터 순회하기

URLSearchParams는 이터러블(iterable)이라서 for...of로 돌리면 [key, value] 형태의 쌍을 하나씩 꺼낼 수 있습니다. 그리고 익숙한 keys(), values(), entries() 메서드도 그대로 사용할 수 있어요:

index.js
Output
Click Run to see the output here.

중복된 키는 여러 번 등장한다는 점에 주의하세요. 예를 들어 tag = web이 먼저 나오고 tag = beginner가 별도 항목으로 또 나옵니다. 실제 쿼리스트링 구조를 있는 그대로 반영한 결과죠.

디버깅용으로 간단하게 객체 형태로 찍어보고 싶다면 Object.fromEntries를 쓰면 됩니다. 단, 중복 키는 합쳐져서 마지막 값만 남는다는 점을 알아두세요:

index.js
Output
Click Run to see the output here.

디버깅 용도로는 괜찮지만, 같은 키가 중복될 수 있는 상황이라면 잘못된 방식입니다.

상대 경로 URL에는 base가 필요하다

new URL("/search?q=js")만 쓰면 에러가 납니다. 상대 경로는 그 자체로 유효한 URL이 아니기 때문이죠. 이럴 땐 두 번째 인자로 base URL을 넘겨주면 됩니다:

index.js
Output
Click Run to see the output here.

이 해석 규칙은 브라우저가 <a href>를 처리할 때 쓰는 것과 똑같다. 앞에 /가 붙으면 호스트 기준 절대 경로, 슬래시가 없으면 현재 경로 기준 상대 경로, ..는 한 단계 위로 올라간다. 설정된 base URL에서 API URL을 조립할 때 정말 유용하다.

브라우저 환경에서는 window.location.href를 base로 넘기면 현재 페이지 URL을 바로 파싱할 수 있다:

const u = new URL(window.location.href);
const page = u.searchParams.get("page") ?? "1";

잘못된 URL 처리하기

URL 생성자는 형식이 잘못된 값이 들어오면 예외를 던집니다. 이게 오히려 장점이 되긴 하지만, 사용자가 직접 입력한 값이나 외부 시스템에서 넘어온 URL을 파싱할 때는 반드시 try/catch로 감싸야 한다는 뜻이기도 합니다.

index.js
Output
Click Run to see the output here.

최신 환경에서는 URL.canParse(input)도 제공됩니다. 단순히 유효성만 확인하고 싶을 때 try/catch로 감쌀 필요 없이 불리언 값으로 바로 판별할 수 있죠:

index.js
Output
Click Run to see the output here.

작은 실전 예제

지금까지 배운 걸 한데 모아볼까요. URL에서 현재 필터 값을 읽어와 살짝 바꾼 뒤, 이동할 새 URL을 만들어내는 흐름입니다.

index.js
Output
Click Run to see the output here.

null을 넘기면 해당 파라미터가 삭제되고, 그 외의 값을 넘기면 새로 설정되거나 덮어쓰기 됩니다. 필터 UI나 페이지네이션, 딥 링크를 만들다 보면 어떤 형태로든 반드시 쓰게 되는 패턴이에요.

핵심 정리

  • new URL(string)은 URL을 의미 있는 조각들로 파싱해 줍니다. 엉뚱한 값이 들어오면 예외를 던져요.
  • url.searchParamsURLSearchParams 객체이고 — get, getAll, set, append, delete, has 메서드로 다룰 수 있습니다.
  • 인코딩은 알아서 처리됩니다. 문자열을 직접 이어 붙이는 경우가 아니라면 encodeURIComponent를 굳이 꺼낼 필요 없어요.
  • 상대 경로를 해석하고 싶다면 두 번째 인자로 기준 URL을 넘기면 됩니다.
  • 신뢰할 수 없는 입력을 검증할 때는 URL.canParsetry/catch를 쓰세요.

URL을 .split('?')로 잘라 보고 싶거나, 정규식으로 쿼리 파라미터를 뽑아내고 싶어질 때마다 이 API들을 먼저 떠올려 보세요. 코드도 짧고, 정확하고, 이미 런타임에 내장되어 있으니까요.

자주 묻는 질문

자바스크립트에서 URL은 어떻게 파싱하나요?

문자열을 URL 생성자에 그대로 넘기면 됩니다: const u = new URL('https://example.com/path?x=1'). 이렇게 만들어진 객체는 protocol, host, pathname, search, hash, 그리고 searchParams까지 바로 꺼내 쓸 수 있어요. 단, 잘못된 URL이면 예외를 던지기 때문에 외부에서 받은 값을 파싱할 때는 try/catch로 감싸는 게 안전합니다.

쿼리스트링 파라미터 값은 어떻게 꺼내나요?

url.searchParams.get('name')을 쓰면 됩니다. 디코딩된 값이 반환되고, 해당 파라미터가 없으면 null이 나와요. ?tag=a&tag=b처럼 같은 키가 여러 번 올 수 있는 경우에는 searchParams.getAll('tag')로 전체 값을 배열로 받아올 수 있습니다.

URL과 URLSearchParams는 어떻게 다른가요?

URL은 프로토콜, 호스트, 경로, 쿼리, 해시까지 URL 전체를 표현하는 객체입니다. 반면 URLSearchParams는 쿼리스트링 부분만 담당해서 a=1&b=2 같은 문자열을 만들거나 파싱할 때 단독으로도 쓸 수 있어요. 모든 URL 인스턴스에는 해당 URL에 연결된 .searchParams 프로퍼티가 들어 있습니다.

쿼리 파라미터를 직접 인코딩해야 하나요?

그럴 필요 없습니다. URLSearchParamsset, append로 값을 넣거나 문자열로 뽑아낼 때 키와 값을 자동으로 인코딩해 줘요. 공백, &, =, 유니코드까지 알아서 처리됩니다. encodeURIComponent는 문자열을 직접 조립할 때만 써야 하는데, 사실 그렇게 수작업할 일 자체가 거의 없어야 합니다.

Coddy로 코딩 배우기

시작하기