이 글에서 정리하는 내용
저는 URL 파라미터가 무엇인지부터 시작해, JavaScript에서 값을 읽고 수정하는 방법, 그리고 퍼블리셔 실무에서 바로 쓸 수 있는 활용 패턴까지 한 흐름으로 정리하겠습니다. 이 글을 끝까지 보면 이벤트 페이지의 탭 유지, 필터 상태 보존, UTM 파라미터 확인, 공유 링크 정리 같은 작업을 문자열 분해 대신 표준 API로 안정적으로 처리하는 기준을 잡을 수 있습니다.
URL 파라미터가 무엇인지 먼저 이해하기

저는 퍼블리싱 실무에서 URL을 볼 때, 먼저 주소 자체와 상태 정보를 분리해서 봅니다. 예를 들어 example.com/event?tab=review&page=2에서 물음표 뒤 영역은 쿼리 문자열이고, 여기 들어 있는 tab=review, page=2 같은 값이 파라미터입니다. 이 구조를 잘 쓰면 현재 탭 상태, 목록 페이지 번호, 검색 조건, 광고 유입 정보 같은 것을 주소에 남길 수 있습니다. 그래서 새로고침을 해도 상태를 어느 정도 유지할 수 있고, 링크를 복사해 전달해도 같은 화면 문맥을 공유하기 쉬워집니다. 결국 URL 파라미터는 주소 뒤에 붙는 부가 문자가 아니라, 현재 화면 문맥을 설명하는 작은 상태 저장소처럼 볼 수 있습니다.
문자열로 직접 자르기보다 URL 객체를 먼저 떠올리기
// 현재 보고 있는 브라우저 주소 전체를 URL 객체로 만듭니다.
// 예: https://example.com/event?tab=review&page=2
const currentUrl = new URL(window.location.href);
// searchParams는 물음표 뒤 파라미터를 다루는 전용 도구입니다.
const params = currentUrl.searchParams;
// pathname은 도메인 뒤의 경로만 가져옵니다.
// 결과 예: /event
console.log(currentUrl.pathname);
// toString()은 물음표를 제외한 파라미터 문자열만 보여줍니다.
// 결과 예: tab=review&page=2
console.log(params.toString());
저는 이 코드에서 먼저 window.location.href가 현재 페이지의 전체 주소라는 점부터 이해하면 좋다고 봅니다. 그 값을 new URL()에 넣으면 브라우저가 이해하기 쉬운 URL 객체가 만들어집니다. 그리고 그 안의 searchParams는 URL 파라미터만 다루는 전용 상자처럼 생각하면 편합니다. 마지막 두 줄은 경로와 파라미터를 각각 분리해서 확인하는 예시입니다. 즉, 이 코드는 주소 전체를 한 번 구조화한 뒤 필요한 부분만 안전하게 꺼내 쓰기 위한 출발점입니다.
location.search와 toString() 결과가 다르게 보이는 이유
// location.search는 물음표를 포함한 원본 형태를 보여줍니다.
// 결과 예: ?tab=review&page=2
console.log(window.location.search);
// URLSearchParams로 파라미터 부분만 읽은 뒤 문자열로 바꿉니다.
const paramsText = new URLSearchParams(window.location.search).toString();
// toString() 결과에는 물음표가 빠집니다.
// 결과 예: tab=review&page=2
console.log(paramsText);
저는 여기서 결과가 달라 보여도 놀라지 않는 것이 중요하다고 봅니다. window.location.search는 원래 주소 문자열의 일부라서 물음표를 포함하고, URLSearchParams.toString()은 파라미터 내용만 다시 문자열로 만든 값이라서 물음표가 없습니다. 그래서 둘은 비슷해 보여도 용도가 조금 다릅니다. 화면에 현재 주소 조각을 그대로 보여주고 싶을 때는 전자를, 파라미터를 조합하거나 다른 링크에 붙일 때는 후자를 떠올리면 됩니다.
JavaScript에서 파라미터 읽기
저는 파라미터를 읽을 때 가장 먼저 get(), has(), getAll() 세 가지를 구분합니다. 단일 값이 필요한지, 존재 여부만 확인하면 되는지, 같은 키가 여러 번 들어올 수 있는지에 따라 선택이 달라지기 때문입니다. 이벤트 페이지나 운영 페이지에서는 단일 탭 값처럼 하나만 있으면 되는 경우가 많지만, 필터 조건은 같은 이름의 키가 여러 번 붙는 경우도 있습니다. 이 구분을 알고 있어야 URL 파라미터를 읽을 때 의도와 실제 결과가 어긋나지 않습니다.
가장 자주 쓰는 읽기 패턴
// 현재 주소를 URL 객체로 변환합니다.
const url = new URL(window.location.href);
// searchParams 안에 있는 URL 파라미터를 다룰 준비를 합니다.
const params = url.searchParams;
// tab이라는 이름의 파라미터 값을 1개 읽습니다.
// 예: ?tab=review 이면 'review'
const tab = params.get('tab');
// page라는 이름의 파라미터 값을 1개 읽습니다.
// 예: ?page=2 이면 '2'
const page = params.get('page');
// utm_source라는 파라미터가 있는지만 true / false로 확인합니다.
const hasUtm = params.has('utm_source');
// 같은 이름의 category가 여러 개 있으면 전부 배열로 가져옵니다.
// 예: ?category=ring&category=watch 이면 ['ring', 'watch']
const categories = params.getAll('category');
// 실제로 어떤 값이 들어왔는지 확인합니다.
console.log(tab);
console.log(page);
console.log(hasUtm);
console.log(categories);
저는 이 코드가 처음에는 길어 보여도, 사실 네 가지 질문을 순서대로 던지는 구조라고 설명하고 싶습니다. 첫째, 현재 주소에서 URL 파라미터를 읽을 준비를 합니다. 둘째, tab과 page처럼 한 개만 들어올 값을 읽습니다. 셋째, utm_source가 아예 붙어 있는지 여부만 확인합니다. 넷째, category처럼 같은 이름이 여러 번 들어올 수 있는 값은 배열로 한꺼번에 받습니다. 즉 get()은 값 하나, has()는 존재 여부, getAll()은 같은 이름의 여러 값을 읽는 용도라고 정리하면 됩니다.
| 메서드 | 언제 쓰면 좋은가 |
|---|---|
| get() | 탭, 페이지 번호, 정렬값처럼 하나만 읽을 때 |
| getAll() | 복수 필터처럼 같은 키가 여러 번 붙을 때 |
파라미터 수정하고 주소에 반영하기
// 현재 주소를 URL 객체로 만듭니다.
const url = new URL(window.location.href);
// URL 파라미터를 수정할 준비를 합니다.
const params = url.searchParams;
// tab 값을 notice로 바꿉니다.
// 기존에 tab이 있으면 덮어쓰고, 없으면 새로 만듭니다.
params.set('tab', 'notice');
// page 값을 1로 바꿉니다.
params.set('page', '1');
// popup 파라미터는 더 이상 필요 없으므로 제거합니다.
params.delete('popup');
// category는 여러 개 붙을 수 있는 값이라고 가정하고 하나 더 추가합니다.
params.append('category', 'ring');
// 주소줄만 새 값으로 교체합니다.
// 페이지를 다시 불러오지는 않습니다.
history.replaceState({}, '', url.toString());
저는 이 코드를 볼 때 수정 동작을 세 갈래로 나눠서 이해하면 쉽다고 봅니다. set()은 값을 새로 지정하는 동작이고, 이미 같은 이름이 있으면 덮어씁니다. delete()는 필요 없는 URL 파라미터를 지우는 동작입니다. append()는 같은 이름의 값을 하나 더 붙이는 동작입니다. 마지막의 history.replaceState()는 바뀐 URL 파라미터를 브라우저 주소줄에 반영하지만, 페이지 자체를 새로 열지는 않습니다. 그래서 탭, 정렬, 필터 같은 상태를 조용히 바꾸고 싶을 때 유용합니다.
기존 URL 파라미터를 유지한 채 일부 값만 바꾸기
// 현재 주소를 그대로 가져옵니다.
const url = new URL(window.location.href);
// 기존 URL 파라미터는 유지하고 sort만 latest로 바꿉니다.
url.searchParams.set('sort', 'latest');
// 페이지를 처음으로 되돌리고 싶다면 page만 1로 바꿉니다.
url.searchParams.set('page', '1');
// 수정된 전체 주소를 문자열로 확인합니다.
const nextLink = url.toString();
console.log(nextLink);
저는 이 패턴을 실무에서 특히 자주 씁니다. 이미 주소에 utm_source나 tab 같은 URL 파라미터가 붙어 있는 상태에서 정렬값 하나만 바꾸고 싶을 때, 기존 URL 파라미터를 통째로 날리지 않고 필요한 값만 수정할 수 있기 때문입니다. 문자열을 다시 조립하는 방식은 빠르게 보이지만, 실제로는 기존 값 누락이 자주 생깁니다. 그래서 운영 페이지에서는 전체를 새로 쓰기보다 기존 URL 파라미터를 유지한 채 일부만 수정하는 방식이 더 안전합니다. 특히 링크 추적값이 있는 페이지에서는 이 차이가 더 크게 느껴집니다.
링크를 새로 만들 때는 URL 객체를 끝까지 활용하기
// 현재 페이지가 아니라 새 목적지 주소를 만듭니다.
// /event/detail 은 상대경로이고, 뒤의 origin은 현재 사이트의 도메인입니다.
const targetUrl = new URL('/event/detail', window.location.origin);
// 새 링크에 필요한 URL 파라미터를 차례대로 붙입니다.
targetUrl.searchParams.set('tab', 'review');
targetUrl.searchParams.set('page', '2');
targetUrl.searchParams.set('utm_source', 'newsletter');
// 완성된 주소를 문자열로 꺼냅니다.
const finalLink = targetUrl.toString();
console.log(finalLink);
저는 링크를 조합할 때도 문자열을 이어 붙이기보다 new URL()로 기준 주소를 만든 뒤 searchParams를 채워 넣는 편을 선호합니다. 이렇게 하면 기존 주소에 물음표가 있는지 없는지, 앰퍼샌드를 붙여야 하는지 같은 사소하지만 자주 발생하는 실수를 줄일 수 있습니다. 운영 배너 링크를 여러 개 관리할 때 특히 안정적입니다. 이 과정을 코드로 만드는 흐름이 일정해지면, 다음 작업자도 규칙을 빠르게 파악할 수 있습니다. 주니어 개발자 관점에서도 읽는 순서가 자연스럽기 때문에 디버깅이 쉬운 편입니다.
실무에 적용하기

저는 퍼블리셔가 URL 파라미터를 단순 개발 문법이 아니라, 운영 효율을 높이는 도구로 보면 좋다고 생각합니다. 실제로는 화면 분기, 링크 보존, 유입 추적, 임시 상태 유지 같은 작업이 훨씬 자주 등장합니다. 특히 마케팅 페이지와 운영 목록 화면은 URL 파라미터를 어떻게 다루느냐에 따라 사용 경험과 분석 정확도가 함께 달라질 수 있습니다.
UTM 유지, 탭 상태 유지, 공유 링크 정리
// 현재 주소에서 URL 파라미터를 읽습니다.
const url = new URL(window.location.href);
const params = url.searchParams;
// 유입 채널 정보를 읽습니다.
// 예: ?utm_source=newsletter 이면 'newsletter'
const utmSource = params.get('utm_source');
// 캠페인 이름을 읽습니다.
// 예: ?utm_campaign=spring_open 이면 'spring_open'
const utmCampaign = params.get('utm_campaign');
// tab 값이 없으면 기본 탭을 info로 사용합니다.
const activeTab = params.get('tab') || 'info';
// 뉴스레터 유입이라면 body에 표시용 데이터를 남깁니다.
if (utmSource === 'newsletter') {
document.body.dataset.inflow = 'newsletter';
}
// 버튼들 중에서 현재 URL 파라미터와 같은 탭만 활성화합니다.
document.querySelectorAll('[data-tab]').forEach((button) => {
if (button.dataset.tab === activeTab) {
button.classList.add('is-active');
}
});
저는 마케팅 랜딩 페이지에서는 UTM 파라미터를 확인해 문구를 바꾸거나, 유입 채널별 배너를 다르게 노출하는 식으로 활용할 수 있다고 봅니다. 또 탭 UI에서는 ?tab=info, ?tab=review처럼 현재 위치를 URL에 남겨두면 새로고침 뒤에도 같은 탭을 다시 보여주기 쉽습니다. 공유 링크를 만들 때는 반대로 불필요한 테스트용 파라미터를 제거하고, 필요한 값만 남겨서 더 깔끔한 주소로 정리할 수도 있습니다. 결국 URL 파라미터는 퍼블리셔가 화면 상태와 유입 맥락을 동시에 관리하는 데 유용한 수단입니다. 위 코드도 순서대로 보면, 읽기 → 기본값 처리 → 화면 반영이라는 흐름으로 이해할 수 있습니다.
정리
저는 URL 파라미터를 단순히 주소 뒤에 붙는 옵션이 아니라, 페이지 상태와 유입 정보를 다루는 실무 도구로 이해하는 것이 중요하다고 봅니다. 퍼블리셔가 자주 만나는 탭 유지, 필터 복원, 페이지 번호 보존, 광고 유입 확인 같은 작업은 대부분 쿼리 문자열과 연결됩니다. 이때 핵심은 문자열을 억지로 자르지 않고 URL과 URLSearchParams를 기준으로 읽고 수정하는 것입니다. 실무에서는 빠르게 처리하는 것도 중요하지만, 다음 수정자도 바로 이해할 수 있는 방식으로 남기는 것이 더 중요합니다. 저는 그래서 get, set, append, delete, replaceState 정도만 정확히 익혀도 운영 페이지 작업 안정성이 꽤 올라간다고 정리하고 싶습니다.
많이 받는 질문
Q. location.search만 써도 되는데 굳이 URL 객체까지 만들어야 하나요?
저는 단순 확인만 할 때는 가능하다고 보지만, 값을 수정하거나 여러 URL 파라미터를 함께 다뤄야 할 때는 URL 객체가 훨씬 안전하다고 봅니다. 읽기와 수정 흐름이 한 번에 이어지고, 유지보수할 때도 의도가 더 잘 보입니다.
Q. set()과 append()는 실무에서 어떻게 구분하면 되나요?
저는 값이 하나만 존재해야 하는 탭, 페이지, 정렬 상태에는 set()을 쓰고, 같은 이름의 값이 여러 개 들어갈 수 있는 복수 필터에는 append()를 씁니다. 이 구분이 안 되면 필터 복원 로직이 어긋날 수 있습니다.
Q. UTM 파라미터는 퍼블리셔도 알아야 하나요?
저는 그렇다고 봅니다. 직접 분석 리포트를 보지 않더라도, 랜딩 페이지를 수정하면서 기존 유입 파라미터를 날리지 않도록 유지해야 하는 경우가 많기 때문입니다. 특히 배너 이동, 버튼 링크 연결, 페이지 내부 이동에서 놓치기 쉬운 부분입니다.