본문 바로가기

Programming/TypeScript, JavaScript

애플리케이션 렌더링 전략 (CSR, SSR)

웹 개발을 할 때 고려해야하는 중요한 사항들 중 하나는 애플리케이션 어디에서 로직 및 렌더링을 구현할지이다. 웹사이트를 만드는데에는 여러 다른 방법들이 있으므로 각자 방식의 특성을 이해하고, 상황에 맞는 전략을 사용해야한다.

렌더링

서버 사이드 렌더링(Server Side Rendering - SSR)

서버 렌더링은 네비게이션의 응답으로 서버의 페이지에 대한 전체 HTML을 생성한다. 이렇게 할 경우 브라우저가 응답 받기 전에 처리되므로 클라이언트에서 데이터 가져오기 및 템플릿을 위한 추가 동작이 필요없다. 서버 렌더링은 보통 빠른 FP(First Paint)와 FCP(Fisrt Contentful Paint)를 생성한다. 서버에서 렌더링을 하게되면, 많은 JS코드들을 클라이언트에게 전송할 필요가 없어진다. 이는 빠른 TTI(Time To Interactive)에 도달할 수 있도록 도와준다. 다양한 디바이스 및 네트워크 조건에서 잘 동작하며 스트리밍 문서 구문 분석(streaming document parsing)과 같은 흥미로운 브라우저 최적화 기능을 제공한다.

https://web.dev/rendering-on-the-web/

사용자는 사이트를 사용하기 전에 CPU 바인딩된 JavaScript가 처리될 때까지 기다릴 필요가 없다. 하지만, 가장 큰 단점은 서버가 페이지를 생성하는 데 시간이 걸리고 이로 인해 TTFB(Time to First Byte)가 느려질 수 있다. 오랜 시간동안 CSR과 SSR을 두고 논쟁이 벌어지곤 있다. 하지만 제일 중요한 것은 특정 페이지에서만 서버 렌더링을 사용하여 이 문제를 해결할 수 있다는 점이다. 어떤 사이트들은 하이브리드 렌더링 기술을 적용하여 성공하였다. 넷플릭스 서버는 상대적으로 정적인 랜딩 페이지를 렌더링하는 반면, 상호 작용이 많은 페이지들은 JS를 prefetching하여 클라이언트 렌더링 페이지를 빠르게 로드할 수 있도록 하고있다.

A Netflix Web Performance Case Study

 

요즘 많은 모던 프레임워크, 라이브러리, 아키텍쳐에서는 클라이언트와 서버 양쪽에서 동일한 애플리케이션을 렌더링 할 수 있도록 제공하고 있다. React 유저들은 Next.js를, Vue 유저들은 Nuxt를, Angular는 Universal을 사용할 수 있다.

정적 렌더링(Static Rendering)

정적 렌더링은 빌드타임에 실행되며, 빠른 FP, FCP, TTI를 제공한다. 서버 렌더링과 달리, HTML을 즉시 생성할 필요가 없기 때문에 일관되게 TTFB(Time to First Byte)를 달성할 수 있다. 정적 렌더링은 각 URL에 대해 별도의 HTML 파일을 미리 생성하는 것을 뜻한다. HTML 응답이 미리 생성되는 경우, 정적 렌더링을 여러 CDN에 배치하여 edge-caching의 이득을 취할 수 있다.

https://web.dev/rendering-on-the-web/

정적 렌더링의 단점 중 하나는 가능한 모든 URL에 대해 개별 HTML 파일을 생성해야된다는 것이다. 미리 URL을 예측할 수 없거나 유니크한 페이지가 많은 사이트는 다소 힘든 전략이 될 수 있다.

정적 렌더링(Static Rendering) vs 사전 렌더링(Pre-Rendering)

정적 렌더링된 페이지는 많은 클라이언트 측 JS를 실행하지 않고도 상호작용할 수 있다. Pre-rendering은 페이지가 상호작용하기 위해서는 클라이언트에서 부팅해야하는 SPA의 FP(First Paint)나 FCP(First Contentful Paing)를 개선한다. 정적 렌더링인지, 사전 렌더링인지 헷갈리는 경우, JS를 비활성화하고 생성된 웹페이지를 로딩해봐라. 정적 렌더링 페이지는 JS가 활성화되지 않아도 대부분의 기능들이 동작해야한다. 사전 렌더링된 페이지의 대부분은 비활성화된다. 또 다른 테스트는, 크롬 개발자도구를 켜서 네트워크의 속도를 줄여서 페이지가 상호작용되기전에 얼마나 많은 JS가 다운로드되었는지 확인해보는 것이다. 사전 렌더링이 정적 렌더링에 비해 더 많은 JS를 필요로 한다.

서버 렌더링(Server Rendering) vs 정적 렌더링(Static Rendering)

서버 렌더링이 언제나 정답이 될 수는 없다. 서버 렌더링은 TTFB를 지연시키거나, 전송되는 데이터를 두 배로 늘릴 수 있다. React의 renderToString() 함수는 싱글 스레드에 동기적인 작업이므로 느릴 수 있다. 서버 렌더링을 올바르게 수행하려면 컴포넌트 캐싱 솔루션을 찾아서 구축하거나, 메모리 사용량을 관리하고, 메모이제이션 기술을 적용하는 등의 시도를 해봐야한다. 일반적으로 동일한 애플리케이션을 서버와 클라이언트에서 각각 여러 번 처리하거나 재구성한다.

서버 렌더링은 각 URL 요청마다 HTML을 생성하지만, 정적 렌더링 콘텐츠를 표시하는 것보다 느릴 수 있다. HTML 캐싱을 통해서 서버 렌더링 시간을 줄일 수 있다. 서버 렌더링의 장점은 정적 렌더링보다 더 많은 실시간 데이터를 가져올 수 있고, 더 완전한 요청에 응답할 수 있다는 것이다. 개인화(Personalization)가 필요한 페이지는 정적 렌더링과는 맞지 않는 예시 중 하나이다.

클라이언트 사이드 렌더링(Client Side Rendering - CSR)

클라이언트 사이드 렌더링은 JS를 사용하여 브라우저에서 직접 페이지를 렌더링하는 것을 뜻한다. 로직, 데이터 fetching, 템플릿 생성, 라우팅등의 모든 기능들이 서버가 아닌 클라이언트에서 처리된다고 보면 된다. 모바일 환경에서 CSR을 빠르게 유지하긴 어려울 수 있다. 최소한의 작업을 수행하고 JS 예산(JavaScript budget)을 엄격하게 지키며 가능한 짧은 RTT(Round Trip Time)으로 가치를 제공하였을 때 순수 서버 렌더링과 비슷한 성능을 가질 수 있다. <lint rel=preload>를 사용하여 중요한 스크립트와 데이터를 더 빠르게 전달할 수 있고, 이를 통해 구문 분석기가 더 빠르게 동작할 수 있다.

https://web.dev/rendering-on-the-web/

애플리케이션의 규모가 커질수록, CSR이 다운로드받아야하는 JS의 규모는 커진다. 페이지의 콘텐츠가 렌더링되기전에 처리되어야하는 서드 파티 코드들이나, 새로운 자바스크립트 라이브러리들이 추가되면서 더더욱 어려워지고있다. 대규모 JS 번들에 의존하는 CSR을 구현할 때, 공격적인 코드 분할을 고려해야하며, lazy load JS를 고려해야한다. <필요할 때, 필요한 것만 로딩하는것> 중요하다.

Rehydration을 통해 SSR과 CSR 통합하기

흔히 SSR이라고 불리는 전략은 클라이언트 측 렌더링과 서버 렌더링 간의 균형을 조정하기 위해 사용된다. 전체 페이지 로드 또는 리로드와 같이 탐색 요청은 애플리케이션을 HTML로 렌더링한 후, 렌더링에 사용되는 JS 및 데이터가 결과 문서에 포함되도록 서버에서 처리한다. 

Rehydration과 SSR을 같이 사용했을 때 가장 큰 단점은, FP를 항샹시킴에도 불구하고, TTI에 부정적인 영향을 미칠 수 있다는 점이다. 서버 사이드 렌더링된 페이지는 클라이언트 측 JS가 실행되고 이벤트 핸들러가 부착되기전까지는 실제로 input에 대한 응답을 할 수 없다. 모바일 환경에서는 해당 작업이 몇 초 또는 몇 분 소요될 수 있다. 실제로 페이지가 로딩된 것 같지만, 클릭이 안되거나 스크롤이 되지 않는 경험을 해본 적이 있을 것이다.

Rehydration의 문제점: 하나의 앱, 가격은 두 배

Rehydration 이슈는 JS로 인한 지연된 상호작용보다 더 큰 문제점을 가져올 수 있다. 서버가 HTML을 렌더링하는데 사용한 모든 데이터를 다시 요청하지않고 클라이언트 측 JS가 서버에서 남은 작업을 정확하게 계속할 수 있도록 하기 위해서, 현재 SSR 솔루션은 일반적으로 UI의 데이터 종속성에서 응답을 직렬화하여 스크립트 태그로 문서에 삽입한다. 결과적으로 HTML 문서에는 중복된 작업들이 많아진다.

네비게이션 요청에 응답하기위해 서버는 애플리케이션의 UI의 설명을 반환하고있으며, 해당 UI를 구성하는데 사용된 소스 데이터와 클라이언트에서 부팅하기 위한 UI 구현의 전체 복사본도 반환하고 있다. bundle.js를 로딩하고 실행이 완료되었을 때에만 UI는 상호작용할 수 있다.

마치며...

위에서 소개한 전략들은 각자의 장단점이 있다. 최소한의 JS를 사용하여 대부분의 HTML을 전송하여 인터렉티브한 사용자 경험을 얻는 것이 최고의 방법이다.


참고 자료: https://web.dev/rendering-on-the-web/

반응형