location · history API 완전 이해

location URL 분해, assign/replace, history.pushState/replaceState, popstate 이벤트, URLSearchParams를 활용한 SPA 라우팅 패턴을 정리합니다.

· 6 min read · PALDYN Team

지난 글에서 window, document, navigator를 살펴봤습니다. 이번에는 URL을 읽고 조작하는 location과 브라우저 히스토리를 제어하는 history API를 정리합니다. 두 API를 이해하면 SPA 라우터의 핵심 원리를 파악할 수 있습니다.


location — URL 파싱과 이동

window.location은 현재 URL의 각 부분을 파싱해 제공합니다.

https://example.com:8080/path/page?q=hello&lang=ko#section
프로퍼티
protocol'https:'
hostname'example.com'
port'8080'
host'example.com:8080'
pathname'/path/page'
search'?q=hello&lang=ko'
hash'#section'
origin'https://example.com:8080'
href전체 URL 문자열
// 현재 URL의 일부만 변경
location.pathname = '/new-path';  // 페이지 이동
location.hash = '#top';           // 스크롤 앵커 (페이지 이동 없음)

hash만 변경하면 페이지가 새로 로드되지 않고 스크롤 위치만 바뀝니다. hashchange 이벤트로 감지할 수 있습니다.

이동 메서드

// 히스토리 스택에 추가 — 뒤로가기로 돌아올 수 있음
location.assign('/about');
// 동일: location.href = '/about';

// 현재 엔트리를 교체 — 뒤로가기 불가
location.replace('/login');

// 페이지 새로고침
location.reload();

assignreplace의 차이는 히스토리 스택입니다. 로그인 후 리다이렉트처럼 “이전 페이지로 돌아가지 않아야 하는” 경우에 replace를 씁니다.


URLSearchParams — 쿼리 파라미터 다루기

location.search를 직접 문자열로 파싱하는 것보다 URLSearchParams가 안전하고 편리합니다.

// 현재 URL의 쿼리 파라미터 읽기
const params = new URLSearchParams(location.search);
params.get('q');           // 'hello'
params.get('lang');        // 'ko'
params.has('page');        // false
params.getAll('tag');      // 복수 값 배열

// 수정
params.set('q', 'world');
params.append('tag', 'js');
params.delete('lang');

// URL에 반영 (replaceState로 히스토리 스택 유지)
history.replaceState({}, '', `?${params}`);
// 새 URL: ?q=world&tag=js

// URL 객체와 함께 사용
const url = new URL('https://example.com?q=test');
url.searchParams.set('page', '2');
console.log(url.href); // 'https://example.com?q=test&page=2'

필터, 정렬, 페이지네이션 UI에서 URL을 상태로 사용할 때 핵심 도구입니다.


history API

history 객체는 브라우저의 세션 히스토리 스택을 조작합니다.

탐색

history.back();       // 뒤로 (= 브라우저 뒤로가기 버튼)
history.forward();    // 앞으로
history.go(-2);       // 두 단계 뒤로
history.go(1);        // 한 단계 앞으로
history.length;       // 히스토리 스택 길이

pushState / replaceState

두 메서드 모두 페이지 로드 없이 URL을 변경합니다. 이것이 SPA의 핵심 원리입니다.

// 시그니처: history.pushState(state, title, url)
// title은 현재 모든 브라우저에서 무시됨 — 빈 문자열 전달

// 새 URL을 히스토리 스택에 추가
history.pushState({ page: 'home' }, '', '/home');

// 현재 URL을 교체 (스택에 추가 안 함)
history.replaceState({ page: 'profile' }, '', '/profile');

state 객체는 직렬화 가능한 데이터를 담을 수 있습니다. popstate 이벤트에서 e.state로 접근합니다.


popstate 이벤트

사용자가 뒤로/앞으로 버튼을 클릭하면 popstate 이벤트가 발생합니다. pushState/replaceState 호출 자체는 popstate를 발생시키지 않습니다.

window.addEventListener('popstate', (e) => {
  console.log(e.state); // pushState/replaceState에 저장한 state 객체
  renderPage(location.pathname);
});

SPA에서 올바른 뒤로가기 처리가 안 된 경우는 대부분 popstate 처리를 빠뜨렸기 때문입니다.


SPA 라우터 미니 구현

// 페이지 렌더링 함수
function renderPage(pathname) {
  const routes = {
    '/': HomeComponent,
    '/about': AboutComponent,
    '/contact': ContactComponent,
  };
  const Component = routes[pathname] ?? NotFoundComponent;
  document.getElementById('app').replaceChildren(new Component().render());
}

// 네비게이션
function navigate(url, state = {}) {
  history.pushState(state, '', url);
  renderPage(new URL(url, location.origin).pathname);
}

// 뒤로/앞으로 처리
window.addEventListener('popstate', () => {
  renderPage(location.pathname);
});

// 링크 클릭 가로채기 (이벤트 위임)
document.addEventListener('click', (e) => {
  const a = e.target.closest('a[href]');
  if (!a || a.origin !== location.origin) return; // 외부 링크 무시
  if (a.target === '_blank') return; // 새 탭 무시
  e.preventDefault();
  navigate(a.href);
});

// 초기 렌더링
renderPage(location.pathname);

location · history API

SPA 라우터 핵심 구현


보안 제약

pushState/replaceState로 변경할 수 있는 URL은 현재 페이지와 같은 origin 내로 제한됩니다. 다른 origin으로 변경하면 SecurityError가 발생합니다.

// 현재 페이지: https://example.com

history.pushState({}, '', '/new-path');           // OK
history.pushState({}, '', 'https://evil.com');    // SecurityError

정리

API역할히스토리 영향
location.assign(url)URL 이동스택 추가
location.replace(url)URL 이동현재 엔트리 교체
history.pushState(s, '', url)URL 변경 (페이지 로드 없음)스택 추가
history.replaceState(s, '', url)URL 변경 (페이지 로드 없음)현재 엔트리 교체
popstate 이벤트뒤로/앞으로 감지

지난 글: window · document · navigator 완전 이해

다음 글: localStorage · sessionStorage 완전 이해


읽어주셔서 감사합니다. 😊