라우팅은 URL을 읽어 어떤 화면을 렌더링할지 결정하는 과정입니다. 이번 글에서는 해시 라우터와 History API 라우터를 순수 JavaScript로 구현합니다. 우선 핵심인 해시 라우터를 이해하고, History API와 내비게이션 가드는 선택 확장으로 보아도 됩니다.
이번 글에서 새로 나오는 용어
- 라우트: 특정 URL과 그 URL에 대응하는 화면 컴포넌트를 묶어 둔 객체입니다.
- 해시 라우터: 주소의
#home처럼 해시 조각을 읽어 화면을 바꾸는 간단한 라우터입니다. - History API:
pushState,replaceState,popstate같은 기능으로 주소 표시줄을 제어하는 브라우저 API입니다. - 내비게이션 가드: 특정 경로로 이동하기 전에 조건을 체크해 입장을 허용하거나 막는 함수입니다.
- popstate 이벤트: 사용자가 뒤로가기·앞으로가기를 눌렀을 때 발생해 현재 URL에 맞는 화면을 다시 렌더하도록 알려줍니다.
핵심 개념
- 라우트(Route, URL-뷰 매핑): URL과 컴포넌트(또는 렌더 함수)를 묶은 객체입니다.
- 해시 라우터(Hash Router,
#기반 네비게이션): URL의#뒤 값을 기반으로 화면을 바꿉니다. 서버 설정이 필요 없고 정적 호스팅에서 자주 사용합니다. - History API(History API, 주소 기록 제어):
pushState,popstate이벤트로 브라우저 주소 표시줄을 바꾸는 방식입니다. 해시 없이 깔끔한 URL을 유지합니다. - 내비게이션 가드(Navigation Guard, 진입 조건 함수): 특정 화면에 들어가기 전에 조건을 검사하는 함수입니다. 로그인 여부나 미저장 상태를 체크합니다.
D2로 보는 라우팅 신호선
nav[사용자 클릭]
router[라우터]
guard[내비게이션 가드]
view[컴포넌트 렌더러]
history[History/hash 상태]
nav -> router: "링크 클릭"
router -> guard: "가드 검사"
guard -> router: "통과 or 차단"
router -> history: "pushState/hash"
router -> view: "renderRoute()"
history -> router: "popstate/hashchange"
그림을 문장으로 풀면 “사용자 클릭 → 라우터가 URL을 확인 → 필요하면 가드로 묻기 → 통과 시 URL을 업데이트하고 화면을 렌더 → 뒤로 가기 시 popstate/hashchange로 다시 렌더” 흐름입니다. 어떤 라우터든 이 순서를 따릅니다.
코드로 확인하기
먼저 해시 라우터로 URL 조각에 따라 화면을 바꿉니다. 본격 코드 전에 location.hash가 언제 바뀌는지만 짧게 확인해 봅니다.
window.addEventListener("hashchange", () => console.log(location.hash));
location.hash = "#tasks";
➡️ hash를 바꾸면 새로고침 없이 이벤트가 발생한다는 사실만 기억하면, 아래 구현이 자연스럽게 이어집니다.
const routes = {
home: () => `<section><h2>홈</h2><p>환영합니다!</p></section>`,
tasks: () => `<section><h2>작업</h2><div id="tasks-root"></div></section>`,
stats: () => `<section><h2>통계</h2><div id="stats-root"></div></section>`,
};
const outlet = document.querySelector("#app");
if (!outlet) {
throw new Error("#app 컨테이너를 찾을 수 없습니다.");
}
function renderRoute(name) {
const view = routes[name];
outlet.innerHTML = view ? view() : `<p>존재하지 않는 페이지입니다.</p>`;
}
function handleHashChange() {
const hash = location.hash.replace("#", "") || "home";
renderRoute(hash);
}
window.addEventListener("hashchange", handleHashChange);
handleHashChange();
해시 라우터는 단 몇 줄로 구현할 수 있고, URL이 바뀔 때마다 hashchange 이벤트가 발생합니다. 즉, “라우트 = hash 문자열 + 템플릿 함수”라는 규칙입니다.
다음은 History API 라우터입니다. 주소 표시줄을 바꾸면서 전체 새로고침을 막습니다.
const historyRoutes = [
{ path: "/", render: () => `<h2>홈</h2>` },
{ path: "/tasks", render: () => `<h2>작업</h2>` },
{
path: "/settings",
guard: () => confirm("설정 페이지로 이동할까요?"),
render: () => `<h2>설정</h2>`,
},
];
function matchRoute(pathname) {
return historyRoutes.find((route) => route.path === pathname);
}
function navigate(pathname) {
const route = matchRoute(pathname);
if (!route) {
outlet.innerHTML = `<p>404 Not Found</p>`;
return;
}
if (route.guard && !route.guard()) return;
history.pushState({}, "", pathname);
outlet.innerHTML = route.render();
}
window.addEventListener("popstate", () => {
const route = matchRoute(location.pathname);
outlet.innerHTML = route ? route.render() : `<p>404 Not Found</p>`;
});
document.querySelectorAll("[data-link]").forEach((link) => {
link.addEventListener("click", (event) => {
event.preventDefault();
const target = event.currentTarget;
if (!(target instanceof HTMLAnchorElement)) return;
navigate(target.getAttribute("href") ?? "/");
});
});
History API 라우터는 주소 표시줄을 바꾸면서도 페이지 전체를 새로고침하지 않습니다. popstate 이벤트는 뒤로 가기/앞으로 가기 버튼을 눌렀을 때 트리거됩니다.
페이지 전환에 로딩 표시를 붙여 사용자에게 진행 상황을 알려 줄 수 있습니다.
const loadingBar = document.querySelector(".router-progress");
async function navigateWithLoading(pathname) {
loadingBar?.classList.add("is-active");
await navigate(pathname);
loadingBar?.classList.remove("is-active");
}
라우터에 로딩 표시를 붙이면 페이지 전환 동안 사용자가 기다릴 수 있습니다. 이 부분은 선택 확장으로, 기본 네비게이션이 안정된 뒤 도입하세요.
왜 중요한가
- URL을 잘 나누면 북마크, SNS 공유, 검색 노출에 유리합니다.
- 프레임워크 라우터도 결국 해시 라우터 또는 History API를 추상화한 것이므로 기초를 이해하면 다른 도구로 옮겨갈 때 빠르게 적응할 수 있습니다.
- 내비게이션 가드를 직접 구현하면 로그인 플로우나 마이페이지 접근 제한 같은 기능을 스스로 만들 수 있습니다.
실습
- 핵심 따라 하기: 해시 기반 라우터를 구현해
#home,#tasks,#stats세 화면을 연결합니다. - 선택 확장하기: History API 버전을 만들고
data-link속성을 가진 내비게이션 링크에 이벤트를 바인딩합니다. - 디버깅: 잘못된 경로를 입력했을 때 404 화면이 렌더되는지 확인하고,
popstate가 언제 호출되는지 콘솔에 기록합니다. - 완료 기준: 해시 라우터와 History 라우터 모두에서 최소 세 개 화면이 정상적으로 전환되고, 가드가 조건에 따라 이동을 막으면 실습이 끝납니다.
마무리
라우팅을 이해하면 페이지 구조를 자유롭게 설계할 수 있습니다. 다음 글에서는 성능 기초를 다시 살펴보고, 측정과 최적화를 통해 대규모 화면에서도 빠르게 동작하도록 만드는 방법을 정리합니다.
💬 댓글
이 글에 대한 의견을 남겨주세요