히스토리 API (Browser History API)

히스토리 API (Browser History API)

브라우저에서 페이지 이동 없이 URL을 조작하고 기록을 관리하려면 History API가 큰 도움이 된다. SPA(싱글 페이지 애플리케이션)에서 자연스러운 네비게이션을 구현할 때 특히 유용하다. 이번에는 History API의 기본부터 심화 활용까지 코드와 함께 자세히 알아보려고 한다.


History API를 잘 활용하면 사용자 경험을 개선하고, 페이지 새로고침 없이도 깔끔한 상태 관리가 가능하다. 하나씩 차근차근 살펴보자.


History API 기본

window.history 객체를 통해 브라우저 기록을 다룰 수 있다. 가장 기본적인 메서드부터 보자:

console.log(window.history.length);
// 현재 세션의 기록 수 출력

history.back();
// 이전 페이지로 이동

history.forward();
// 다음 페이지로 이동

history.go(-2);
// 기록에서 두 단계 뒤로 이동

back, forward, go로 기록을 이동할 수 있다. 단순하지만 강력한 기능이다.


1. pushState로 기록 추가

pushState를 사용하면 페이지 새로고침 없이 URL을 변경하고 기록을 추가할 수 있다:

history.pushState(
    { page: "home" }, // 상태 객체
    "홈", // 제목 (현재는 무시됨)
    "/home" // URL
);

console.log(history.state);
// { page: "home" }

console.log(window.location.pathname);
// "/home"

URL이 /home으로 바뀌었지만 페이지는 새로고침되지 않았다. 상태 객체는 나중에 유용하게 쓰인다.


2. replaceState로 기록 수정

replaceState는 새 기록을 추가하지 않고 현재 기록을 수정한다:

history.pushState({ page: "about" }, "소개", "/about");
history.replaceState({ page: "about-updated" }, "소개 수정", "/about-us");

console.log(history.state);
// { page: "about-updated" }

console.log(location.pathname);
// "/about-us"

pushState로 기록이 추가된 후, replaceState로 현재 상태와 URL을 업데이트했다.


3. popstate 이벤트로 상태 감지

사용자가 뒤로 가기나 앞으로 가기를 누르면 popstate 이벤트가 발생한다:

window.addEventListener("popstate", (event) => {
    if (event.state) {
        console.log("상태: ", event.state);
    } else {
        console.log("상태 없음");
    }
});

history.pushState({ page: "profile" }, "프로필", "/profile");
history.pushState({ page: "settings" }, "설정", "/settings");
history.back();
// "상태: { page: 'profile' }"

뒤로 가기를 눌렀을 때 이전 상태를 감지해서 로그로 출력했다.


4. 간단한 SPA 구현

History API로 간단한 SPA 네비게이션을 만들어보자:

const content = document.createElement("div");
document.body.appendChild(content);

function renderPage(state) {
    const page = state ? state.page : "home";
    content.innerHTML = `현재 페이지: ${page}`;
}

window.addEventListener("popstate", (event) => {
    renderPage(event.state);
});

const links = [
    { path: "/home", state: { page: "home" } },
    { path: "/about", state: { page: "about" } },
    { path: "/contact", state: { page: "contact" } }
];

links.forEach((link) => {
    const a = document.createElement("a");
    a.href = link.path;
    a.textContent = link.state.page;
    a.addEventListener("click", (e) => {
        e.preventDefault();
        history.pushState(link.state, "", link.path);
        renderPage(link.state);
    });
    document.body.appendChild(a);
    document.body.appendChild(document.createElement("br"));
});

renderPage(history.state);

링크를 클릭하면 URL이 바뀌고 콘텐츠가 업데이트된다. 뒤로 가기와 앞으로 가기도 자연스럽게 동작한다.


5. 쿼리 파라미터와 조합

URL에 쿼리 파라미터를 추가해서 상태를 더 풍성하게 관리할 수 있다:

function renderContent(state, search) {
    const params = new URLSearchParams(search);
    const id = params.get("id") || "없음";
    console.log(`페이지: ${state.page}, ID: ${id}`);
}

window.addEventListener("popstate", (event) => {
    renderContent(event.state, location.search);
});

history.pushState(
    { page: "product" },
    "",
    "/product?id=123"
);
// "페이지: product, ID: 123"

history.pushState(
    { page: "product" },
    "",
    "/product?id=456"
);
// "페이지: product, ID: 456"

history.back();
// "페이지: product, ID: 123"

쿼리 파라미터를 활용해서 상태에 추가 정보를 담았다.


6. 동적 라우팅 처리

경로에 동적인 값을 넣어서 라우팅처럼 사용할 수 있다:

function getPageFromPath(pathname) {
    const match = pathname.match(/\/user\/(\d+)/);
    if (match) {
        return { page: "user", id: match[1] };
    }
    return { page: "home" };
}

function render(state) {
    console.log(`페이지: ${state.page}, ID: ${state.id || '없음'}`);
}

window.addEventListener("popstate", (event) => {
    const state = event.state || getPageFromPath(location.pathname);
    render(state);
});

history.pushState(
    { page: "user", id: "100" },
    "",
    "/user/100"
);
// "페이지: user, ID: 100"

history.pushState(
    { page: "user", id: "200" },
    "",
    "/user/200"
);
// "페이지: user, ID: 200"

history.back();
// "페이지: user, ID: 100"

정규식을 사용해서 동적 경로를 파싱하고 상태를 생성했다.


7. 상태 복원과 초기화

페이지를 새로고침했을 때도 상태를 복원할 수 있다:

function initPage() {
    const state = history.state || getPageFromPath(location.pathname);
    render(state);
}

window.addEventListener("popstate", (event) => {
    render(event.state);
});

history.pushState({ page: "settings" }, "", "/settings");
initPage();
// "페이지: settings, ID: 없음"

새로고침 후에도 history.state나 경로를 통해 상태를 복원했다.


8. 성능과 사용자 경험에 미치는 영향

History API가 코드와 사용자 경험에 어떤 영향을 주는지 보자:

- 성능: 페이지 새로고침 없이 상태를 변경하니 로드가 빠르다.

- 사용자 경험: 자연스러운 네비게이션과 뒤로 가기 지원으로 편리함이 커진다.

pushStatepopstate를 조화롭게 사용하면 SPA의 핵심 흐름을 만들 수 있다.


마무리

History API는 URL과 브라우저 기록을 유연하게 다룰 수 있게 해준다. 기본 메서드부터 동적 라우팅, 상태 복원까지 활용하면 SPA를 비롯한 다양한 환경에서 강력한 도구가 된다.


+ Recent posts