routing 라이브러리의 도움 없이 javascript만으로 routing 기능을 구현해보자. 여러가지 방법이 있겠지만 그중에서도 pjax 방식을 사용하여 routing 기능을 구현해 볼것이다.
pjax(pushState + ajax)
는 몇가지 routing 방식의 개선을 거듭하여 이전 방식인 hash 방식의 단점인 SEO를 개선하기 위해 나온 방식이다. HTML5의 history API인 pushState와 popstate 이벤트를 이용하여 구현할 수 있다. pushState와 popstate는 IE10 이상부터만 작동한다고 한다.
<nav>
<ul>
<li><a href="/home"></a></li>
<li><a href="/service"></a></li>
</ul>
</nav>
위 html 코드를 보면 정통적 링크 방식과 동일한 형태로 href 속성의 값이 구성되는 것을 확인할 수 있다.
풀 소스는 참조한 페이지인 poiemaweb에서 확인하는 것으로 하고 핵심 로직만 추려봤다.
const routes = [
{ path: '/', component: Home },
{ path: '/service', component: Service },
{ path: '/about', component: About },
];
const render = async path => {
try {
const component = routes.find(route => route.path === path)?.component || NotFound;
root.replaceChildren(await component());
} catch (err) {
console.error(err);
}
};
navigation.addEventListener('click', e => {
// server 요청을 막기위해 preventDefault 호출
e.preventDefault();
const path = e.target.getAttribute('href');
window.history.pushState({}, null, path);
render(path);
});
window.addEventListener('popstate', () => {
render(window.location.pathname);
});
render(window.location.pathname);
pjax 방식은 navigation의 클릭 이벤트를 캐치하고 preventDefault()
를 호출하여 서버요청을 방지한다. 그리고 history api의 pushState 메서드를 호출하여 url을 변경하게 된다.
pushState 메서드는 브라우저의 세션 기록 스택에 상태를 추가한다. interface는 아래와 같다.
history.pushState(/* state */, /* title */, /* url */);
새로운 세션 기록 항목을 생성하고 활성화 한다는 점이 hash 방식과 비슷하지만 pushState는 몇가지 장점을 가지고 있다.
hash | pushState | |
---|---|---|
URL 변경 방식 | hash만 변경 가능 | 동일 출처만 같다면 URL 변경 자유 |
새 세션 기록 생성을 위한 URL 변경 방식 | 현재 hash 값과 다른 hash 값 설정 | pushState 호출(URL 변경 필수아님) |
임의의 데이터 전달 방식 | 데이터를 인코딩 하여 짧은 문자열로 전달 | 세션 상태 항목을 통해 전달 |
pjax 방식은 서버 렌더링 방식과 ajax 방식이 혼재되어 있으므로 서버의 지원이 필요하다.
const express = require('express');
const path = require('path');
const app = express();
const port = 5004;
app.use(express.static(path.join(__dirname, 'public')));
// 브라우저 새로고침을 위한 처리 (다른 route가 존재하는 경우 맨 아래에 위치해야 한다)
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'public/index.html'));
});
app.listen(port, () => {
console.log(`Server listening on http:/localhost:${port}`);
});
브라우저 새로고침 시 서버는 index.html을 전달하고 클라이언트는 window.location.pathname를 참조해 다시 라우팅한다.
react-router-dom은 정말 pushState를 사용하여 SPA를 구현하는지 궁금했다.
본격적으로 파고 들기 위해 react-router-dom 프로젝트를 cloning 해보았는데, 재미있게도 하나의 프로젝트로 묶여 있는 것을 확인할 수 있었다.
npmjs에서 Github Repository를 따라가보면,
react-router라는 프로젝트로 이동된다.
안에 내용을 분석해 보니 이 react-router 프로젝트는 packages라는 폴더를 가지고 있고, 대외적으로 패키지 매니저를 통해 설치되는 라이브러리 명들을 이 pakcages 폴더 아래에서 찾아 볼 수 있었다.
위 이미지에서 살펴보면, react-router-dom
, react-router-native
, react-router
등이 있는 것을 확인할 수 있다. 해당 패키지들이 실제로 번들링 되어서 올라가는 것으로 확인된다.
pushState를 사용한 로직을 검색해 보니, packages 폴더 아래 router
라는 패키지 아래에 history
파일 내에서 존재 하는 것을 확인할 수 있었다.