개발자라면 개발하고 있는 애플리케이션 전체의 아키텍처 및 구조에 영향을 미칠 만한 것을 판단하고 정의하기 마련이다. 프론트엔드 개발자가 직면하는 가장 핵심적인 의사 결정 중에 하나는 브라우저 상에서 웹 어플리케이션의 로직과 렌더링을 구현하는 것이다.
Rendering이란 간단하게 말하자면 서버로부터 markup language file인 HTML 파일, 스타일링이 정의된 CSS 파일, Javascript 파일 등을 받아 브라우저에 이를 그래픽 형태로 뿌려주는 것이다.
우리가 사용하는 브라우저는 저마다 렌더링 엔진(Rendering Engine)을 가지고 있다. Firefox는 'Gecko'라는 것을 사용하며, Safari는 'Webkit', Chrome은 Webkit에서 fork 된 버전인 Blink 엔진을 자체적으로 구현해서 사용한다. 이 렌더링 엔진들은 웹 표준에 따라 개발자들이 작성한 문서를 브라우저에 로드하지만, 세부 표준이나 렌더링 알고리즘, 방식에 차이가 있을 수 있어 각 엔진 마다 개발자의 의도와는 다르게 웹페이지가 동작할 수 있다.
웹 페이지가 렌더링 과정을 간단하게 설명하자면 4가지 스텝으로 나눌 수 있다. 첫 번째로 DOM을 생성하고, 두 번째로 Render Tree를 형성한 다음, Layout과 Paint 단계를 갖는 것이다.
제일 먼저 서버로 부터 HTML과 CSS를 다운로드를 받는데 이 둘은 단순한 텍스트 이므로 연산과 관리가 유리하게 Object Model로 만든다. 그렇게 해서 생성되는 것이 HTML 문서를 파싱한 DOM(Document Object Model)과 CSSOM(Cascading Style Sheet Object Model)이다.
출처: 구글 개발자 사이트: 객체모델 생성
출처: 구글 개발자 사이트: 객체모델 생성
이 두 가지 object model을 시각화 한 것이 위 그림이다. 각각 DOM 트리와 CSSOM 트리를 나타내며 자세한 과정은 위 출처를 확인하면 알 수 있다.
위에서 형성된 두가지 object model은 하나로 합쳐져서 Render tree 를 형성하게 된다. 이 Render tree에는 스타일 정보도 함께 있고 실제 화면에 나타나는 노드들로만 구성되게 된다. 즉 여기서 display: none
로 스타일링 된 노드와 head
태그는 렌더링 트리 형성에서 제외된다. 한편,visuality: invisible
비슷하게 동작하지만 공간은 차지하고 요소만 감추기 때문에 Render tree에는 포함된다.
출처: 구글 개발자 사이트: 렌더링 트리 생성, 레이아웃 및 페인트
이렇게 생성된 Render tree 각각의 노드들이 가지고 있는 스타일과 속성에 따라, 브라우저 화면의 뷰포트(viewport)에 출력될 정확한 위치와 크기를 계산하는 단계가 이 Layout 단계이다. 여기서 Viewport 란, 그래픽이 표시되는 브라우저의 영역, 크기를 말한다. 모바일은 스크린(디스플레이)의 크기, 컴퓨터는 브라우저 창의 크기에 따라 달라진다. %, vh, vw 같은 상대적인 척도로 요소를 스타일링 했을 때, viewport의 크기가 달라지는 것을 고려하여 계산하고, 이후 실제 화면에는 pixel 단위로 변환되어 다음 Paint 단계에서 그려진다.
출처: 구글 개발자 사이트: 렌더링 트리 생성, 레이아웃 및 페인트
Layout 단계에서 계산된 것을 바탕으로 Paint 단계에서는 이를 실제 픽셀 값으로 대체하여 화면에 그린다. 처리되는 것들은 텍스트, 색, 이미지, 그림자, 애니메이션 효과 등이다.
만약 처리해야 하는 스타일이 복잡하다면 Paint 단계 소요시간은 길어진다.
Paint 단계를 거치더라도 렌더링 과정이 다 끝난 것만은 아니다. Javascript에 따른 액션, 이벤트 조작이 있을 시 그에 영향을 받는 자식 노드나 부모 노드를 포함하여 Layout 과정을 다시 수행한다. 이 과정이 Reflow이며, Reflow 뒤에는 Repaint도 대개 다시 일어난다.
이 과정이 우리가 웹 사이트에 접속하면 브라우저에서 서버와 HTTP 통신이 일어나고 Request와 Response를 주고 받은 다음 일어나는 과정이라고 할 수 있다. 예전에는 화면에 어떤 변화를 주기 위해 화면 전환을 하면 그 때 마다 매번 서버로 부터 새로운 파일을 전송 받고 다시 렌더링하는 과정을 거쳐야 했으므로 성능적인 문제도 많았고, 전환될 때마다 flickering(깜빡임) 등 문제가 있었다.
웹 어플리케이션이 단순하면 Layout, Paint 단계에서 나아가 이런 reflow나 repaint 도 cost가 그리 크지 않겠지만, 우리는 점차 복잡한 웹 어플리케이션을 요구하고 구축해왔다. 그 과정에서 많아지는 reflow, repaint로 인한 re-rendering cost를 해소하기 위해 고민하며 등장한 것이 Ajax, jQuery를 거쳐 React나 Angular 같은 라이브러리/프레임워크이다.
이런 프레임워크들을 논하기에 앞서, 알고가야 할 웹 어플리케이션 구축 방법/개념 세가지. CSR, SSR, SSG를 각각 알아보자.
서버에서 모든 정보를 다 받아오게 기다리면 늦어! 서버에서 틀이 되는 파일 하나 내려 주면 거기에 내가 알아서 화면에 그릴게
CSR은 Client-Side Rendering(클라이언트 측 렌더링)의 약자로, 처음 브라우저에서 애플리케이션을 렌더링 할 때 비어 있는 html을 응답 받고, client 단에서 Javascript를 사용하여 DOM을 조작, 직접 렌더링하는 것을 의미한다. 이 때 모든 로직, 데이터 가져오는 것, 템플릿 및 라우팅 등은 서버가 아닌 클라이언트에서 처리 된다.
출처: 드림코딩 엘리님 유튜브 영상
예를 들어 위 그림처럼 www.hello.com 사이트에 접속하면 서버에서 index.html이라는 기본 정보만 내려주고, client side 에서 정의한 app.js로 이 index.html 안에 동적으로 그리겠다는 개념이 CSR이다.
대표적으로 React가 이 CSR 방식을 채택하고 있으며, 이 방식으로 개발한 어플리케이션이 SPAs(Single Page Applications) 이다. 첫 single page 만 서버로 부터 내려받아와서 이후 모든 것은 클라이언트 사이드에서 책임지겠다 하는 어플리케이션인 것이다.
이렇게 하면 단일 페이지에서 랜더링 속도가 기존 정적 방식 보다 빠르다는 장점을 갖는다.
빠르다는 장점을 갖지만 CSR도 단점을 갖고 있다.
구글 같은 여러 검색 엔진 프로그램은 웹사이트의 HTML 소스를 읽어드리고 이를 인덱싱 한 다음 필터링을 넣거나 키워드 검색이 용이하게 한다. 그러나 CSR 방식으로 만든 웹 사이트는 최초에 빈 HTML만 렌더링하기 때문에 검색 크롤러 들이 컨텐츠 요소를 제대로 감지하지 못한다. 물론 구글의 경우 최근에 검색 엔진 능력을 강화하며 보완했다고 하지만 여전히 검색 엔진 최적화는 CSR의 대표적인 단점이라고 할 수 있다.
클라이언트 사이드에서 페이지를 렌더링하기 위해 DOM을 조작해야하고, 그러기 위해선 자바스크립트 소스 코드가 많아질 수 밖에 없다. 그렇다 보니 처음 렌더링 시 페이지 로드에 필요한 모든 JS 코드를 불러오는 과정에서 그 양이 아주 많으면 초기 빈페이지만 화면에 출력되는 현상이 있다. 한번 렌더링을 한 후에는 필요한 부분만 렌더링 하여 그 후 속도는 좋지만 첫 페이지 로딩 속도가 치명적인 단점이다. 물론 요즘은 code splitting 테크닉을 이용해서 어느 정도 보완은 가능하다.
서버에서 HTML,CSS, Javascript 다 처리하고 내려주는 게 낫지 않아? 검색 결과도 더 최적화 잘 되고 좋다고~?
SSR은 Server Side Rendering(서버 측 렌더링)의 약자이며, 옛날 방식이 아닌, modern 한 방식으로 우선 전제를 하자. 브라우저가 서버에 페이지를 요청하면 서버가 기존에 정의된 데이터로 HTML을 구성하여 브라우저에 응답하고, 브라우저는 이를 그대로 화면에 렌더링하는 방식이다.
출처: 드림코딩 엘리님 유튜브 영상
SSR은 CSR의 단점을 다 커버한다. 첫 페이지 로딩이 빨라지고, 모든 컨텐츠가 HTML에 다 담겨 있어 좀 더 효율적인 SEO가 된다.
이런 SSR도 단점이 여전히 존재하는데, 이들은 다음과 같다.
정적 사이트에서 발생했던 페이지 전환시 서버에서 즉각적으로 페이지 생성하면서 생기는 화면 깜빡임 문제가 여전히 존재한다.
서버에서 매 번 동적으로 계산하여 페이지를 렌더링하기에 서버 부하가 커지기 쉽다. 비용이 늘어나는 것도 마찬가지다.
SSR의 제일 치명적인 단점이라고 할 수 있는게 바로 이 애플리케이션 내 상호작용(interaction)이다. 빠르게 확인 가능하지만, 동적으로 처리할 수 있는 자바스크립트 파일을 서버로부터 미처 다움을 다 받지 못하여 유저가 클릭했는데 응답을 늦게 받거나 못 받을 가능성도 존재하게 된다. TTV(Time to View), TTI(Time to Interact) 같은 지표로 확인가능하다.
CSR, SSR 각각 장단점이 이렇듯 있는데, 이 장단점을 서로 보완할 수 있는, 정적 사이트 형태 제공은 어떨까?
SSG는 Static Site Generation(정적 사이트 생성)의 약자로, 정적 사이트 생성기를 함께 접목 시켜 랜더링이 필요하지 않을 부분, 간단히 말해 모든 유정에게 항상 동일한 화면만 띄울 부분은 정적으로 만들어버리고 이 파일을 따로 저장한 다음 불러와서 바로 그려버려리자는 취지로 나타났다.
예를 들어 사이트의 정적인 about 페이지는 모든 유저에게 같은 화면만 보이기에 매번 동적으로 생성할 필요가 없으며 한번만 생성한 후 이를 CDN에 저장했다가 필요시 로드하는 식으로 해서 빠르게 제공하는 것이다. 여기에 갖고 있는 자바스크립트 파일도 함께 로드하여 동적 요소도 충분히 커버하는 것이다.
출처: 드림코딩 엘리님 유튜브 영상
위 사진과 같이 React로 만든 웹 애플리케이션을 Gatsby와 같은 정적 사이트 생성기를 이용해 static HTML 파일들을 빌드, 배포 한 뒤 이를 서버에서 로드해서 client 단에 보이는 방식이 SSG 방식이다. Gatsby 외에 Next.js도 pre-rendering과 no-rendering 테크닉을 이용해 이 SSG를 지원하고 있고, SSG 방식과 SSR 방식을 선택해서 할 수 있다. 자세한 구동 방식은 Next.js 공식 문서를 참고하면 파악 가능하다.
Next.js 에 관해서는 추후 따로 포스트로 소개하겠다.
이렇게 현대 트렌드를 따라가기 위해 프론트엔드 개발자가 알아야 할 기본적인 web rendering과 rendering 방식들에 대해 알아보는 시간을 가졌다. 이것들은 개념만 익힌다고 되는 것이 아닌, 직접 코드를 만져보며 동작 원리를 이해해야 체득 되는 것이다. 그렇기 때문에 이 개념을 가지고 실습을 하는 것이 중요하며, 리액트 실습 해 보았던(지난 포스트들 참고) 것을 바탕으로 Next.js, Gatsby, React 상태 관리 라이브러리 등 만져보며 빠삭하게 이해해야 겠다.
정리가 잘 되어있어서 잘 봤습니다~ 렌더링 지식 다시한번 온고하고 갑니다~