웹사이트에서 사용자의 반응(링크클릭)등에 빠른 속도로 반응하는 것은 항상 웹개발자들의 꿈이다. 느려터진 웹사이트는 사용자를 짜증나게하고 이탈률을 증가시킨다.
가장 속도를 빠르게하는 것은 원격서버를 통하지 않고 로컬에서 다 해결하면 된다!
하지만 이것은 불가능한 일이다. 그래서 대안으로 나오는 것이, 전반적인 UI에 대해서는 로컬이 담당하고 필요한 데이터만 최소한으로 서버와 통신하는 방법이다. 이것이 SPA(Single Page Application) 방식에서 주로 쓰이는데 이러한 툴들로는 AngularJS나 ReactJS 등이 요즘 각광을 받고 있다.
하지만 SPA는 재빠른 반응을 구현할 때에는 최적이지만, 일종의 컨텐츠 서비스(커뮤니티나 블로그 등)은 상대적으로 SEO가 더 중요하며, 따라서 페이지별 독립된 트래픽이 발생할 수 있기에 거대한 하나의 번들 파일을 받게하는 것은 효과적이지 못하다. 물론 SPA도 SEO최적화가 가능하다.
Requirements
- SEO가 가능하여야 한다.
- Browser History를 존중하여야 한다.
- 페이지 반응 속도가 빨라야 한다.
- 개발 및 유지보수가 어렵지 않아야 한다.
pjax
https://github.com/defunkt/jquery-pjax
pjax는 아주 단순하다. 특정 셀렉터(앵커태그)에 pjax를 바인딩하면, 해당 링크가 클릭되었을 때 타겟 주소에 대해서 서버에 요청을 하고, 바꿀 부문만 받아와서 바꿀 부분의 DOM만 갈아치우면서, pushState(HTML5)를 이용하여 브라우저의 주소도 변경을 해준다. 무언가 복잡해보이지만 필요한 부분만 받아와서 갈아치운다라는 원리는 ajax와 같다(실제로 내부적으로는 ajax를 쓴다). 이렇게 함으로써 위의 요구사항을 모두 만족시킬 수 있다.
Usage
아래는 실제 자주 쓰고 이곳 타일 블로그에도 사용한 스크립트이다. pjax에서 제공하는 이벤트와 메써드를 사용하여 기본 사용법을 약간 변형을 하였다. 서버를 하나도 건드리지 않고도 속도 최적화를 할 수 있는 옵션이다. 각각의 설명은 스크립트의 주석에 달아 놓았다.
<script>
$(document).on('click', 'a.pjax', function(e){ // pjax라는 클래스를 가진 앵커태그가 클릭되면,
$.pjax({
url: $(this).attr('href'), // 앵커태그가 이동할 주소 추출
fragment: '#posts-container', // 위 주소를 받아와서 추출할 DOM
container: '#posts-container' // 위에서 추출한 DOM 내용을 넣을 대상
});
return false;
});
// for google analytics
$(document).on('pjax:end', function() {
ga('set', 'location', window.location.href); // 현재 바뀐 주소를
ga('send', 'pageview'); // 보고한다
});
</script>
Speed Check
pjax를 안 썼을 때와 썼을 때를 이 블로그의 페이지 전환을 통해서 테스트를 해본 결과이다. 10번씩 각각 테스트하고 최소값과 최대값을 제외하고나서 평균을 내보았다. 대략 2.4배의 차이가 난다. 사실 네트워크 레이턴시가 있기 때문에 캐시하지 않는한 이 값이 극단적으로 차이가 나지는 않을것이지만, 중요한 것은 pjax로 하면 리프레시로 인해서 화면 깜빡임 자체가 없어지기 때문에 체감 속도는 훨씬 나게 된다.
Redirection(ms) | pjax(ms) |
---|---|
1005 | 450 |
872 | 470 |
888 | 441 |
1104 | 318 |
1243 | 402 |
1221 | 324 |
882 | 315 |
888 | 564 |
avg. 1012 | avg. 410 |
Server Optimization
위까지만 걸어줘도 페이지 전환 속도가 극적으로 향상되는 것을 느낄 수 있다. 하지만 이 경우에 헤더와 푸터 등 모든 마크업을 받아온 이후에 표시할 부문만 출력하기 때문에 완전히 최적화된 것은 아니다. 서버측에서 pjax로 들어온 것을 인식하여 헤더푸터를 제외하고 본문만 내려보는내 코드를 추가하면 된다. 만약 타겟에서 사이드바가 제외된다면, 사이드바에 들어가는 서버 쿼리도 제외시키면 더욱 향상이 될 것이다.
// ruby
def index
if request.headers['X-PJAX']
render :layout => false
end
end
pjax는 get 파라미터로 _pjax=true
도 전송을 해주니, 이 값을 인식하여 헤더푸터를 날려주어도 된다. 이 포스팅에서 대해서 자세히 다루지 않는 것은 언어마다 템플릿 마크업을 어떻게 했느냐에 따라서 너무 천차만별이고, 사실 서버 자체에서도 어느정도 캐싱 등의 대안들을 마련했을 터이니, 여기 예시처럼 몽땅받아서 특정 DOM만 추출하는 방식을 쓰는 것도 생각보다 나쁘지는 않다.
Consideration
본문쪽의 내용은 이제 동적으로 생성되는 컨텐츠 영역이 되었다(위의 경우 #posts-container
). 따라서 기존 정적 페이지일 때에 걸어주었던 바인딩하는 방식을 최소한 저 컨테이너에 물리도록 교체를 해주어야 한다.
Cons
없다. 아주 HTML5의 기본스펙을 쓰기 때문에, 나중에 이 기술이 갑자기 사라질 일도 없다. 게다가 pushState
를 쓸 수 없는 구닥다리 익스플로러 버전에서는 그냥 일반적인 페이지 이동이 발생하니, 호환성을 걱정할 필요도 없다(즉, 크롤러 입장에서는 그냥 일반 웹페이지처럼 작동). 이미 SEO가 되어있는-단독으로 접근 가능한 페이지-를 타겟팅하므로 SEO도 문제 없다. 굳이 따지자면 개발시 이 링크를 pjax로 작동시킬 것인가 안 시킬 것인가 고민 되는 정도가 있다
관련링크
- https://github.com/defunkt/jquery-pjax : 본문에서 사용한 pjax 라이브러리
- https://github.com/turbolinks/turbolinks : 비슷한 방식으로 구동하는 라이브러리같습니다. - 김동혁님 제보