웹에서 렌더링

애플리케이션에서 로직과 렌더링을 어디에 구현해야 하나요? 서버 측 렌더링을 사용해야 합니까? 수분 보충은 어떨까요? 답을 찾아보겠습니다.

아디 오스마니
애디 오스마니

개발자는 애플리케이션의 전체 아키텍처에 영향을 미치는 결정을 내리는 경우가 많습니다. 웹 개발자가 결정해야 하는 핵심 사항 중 하나는 애플리케이션에서 로직과 렌더링을 어디에 구현할 것인지입니다. 웹사이트를 구축하는 방법에는 여러 가지가 있기 때문에 어려운 일일 수 있습니다.

Google은 지난 몇 년 동안 Chrome에서 대규모 사이트와의 대화를 통해 이 공간을 파악했습니다. 개략적으로 말하자면, 개발자는 전체 리하이드레이션 접근 방식보다는 서버 측 렌더링이나 정적 렌더링을 고려하는 것이 좋습니다.

이 결정을 내릴 때 어떤 아키텍처를 선택하는지 더 잘 이해하려면 각 접근 방식을 확실하게 이해하고 이에 대해 이야기할 때 사용할 일관된 용어를 알고 있어야 합니다. 이러한 접근 방식 간의 차이점을 통해 성능 측면에서 웹 렌더링의 장단점을 확인할 수 있습니다.

용어

렌더링

  • 서버 측 렌더링 (SSR): 클라이언트 측 또는 범용 앱을 서버의 HTML로 렌더링하는 것입니다.
  • 클라이언트 측 렌더링 (CSR): DOM을 수정하기 위해 JavaScript를 통해 브라우저에서 앱을 렌더링합니다.
  • 리하이드레이션: 클라이언트에서 자바스크립트 뷰를 '부팅'하여 서버에서 렌더링한 HTML의 DOM 트리와 데이터를 재사용합니다.
  • 사전 렌더링: 빌드 시 클라이언트 측 애플리케이션을 실행하여 초기 상태를 정적 HTML로 캡처합니다.

성능

서버 측 렌더링

서버 측 렌더링은 탐색에 대한 응답으로 서버 페이지의 전체 HTML을 생성합니다. 이렇게 하면 브라우저에서 응답을 받기 전에 데이터 가져오기 및 템플릿 작성이 처리되므로 추가로 왕복하지 않아도 됩니다.

서버 측 렌더링은 일반적으로 빠른 FCP를 생성합니다. 서버에서 페이지 로직을 실행하고 렌더링하면 클라이언트에 많은 자바스크립트를 전송하지 않아도 됩니다. 이렇게 하면 페이지 로드 중에 기본 스레드가 자주 차단되지 않으므로 페이지의 TBT를 줄여 INP가 낮아질 수도 있습니다. 기본 스레드가 차단되는 빈도가 줄어들면 사용자 상호작용이 더 빨리 실행될 수 있는 기회가 많아집니다. 서버 측 렌더링을 사용하면 사용자의 브라우저로 텍스트와 링크만 전송하게 되므로 이는 타당합니다. 이 접근 방식은 다양한 장치 및 네트워크 조건에 적합할 수 있으며 스트리밍 문서 파싱과 같은 흥미로운 브라우저 최적화를 제공합니다.

FCP 및 TTI에 영향을 미치는 서버 측 렌더링 및 JS 실행을 보여주는 다이어그램

서버 측 렌더링을 사용하면 사용자가 사이트를 사용하기 전에 CPU에 바인딩된 JavaScript가 실행될 때까지 기다릴 가능성이 줄어듭니다. 서드 파티 JS 사용을 피할 수 없는 경우에도 서버 측 렌더링을 사용하여 퍼스트 파티 자바스크립트 비용을 줄이면 나머지 작업에 더 많은 예산을 사용할 수 있습니다. 하지만 이 접근 방식에는 한 가지 잠재적인 단점이 있습니다. 즉, 서버에서 페이지를 생성하는 데 시간이 걸리므로 TTFB가 높아질 수 있습니다.

서버 측 렌더링이 애플리케이션에 충분한지 여부는 주로 빌드하는 환경의 유형에 따라 다릅니다. 서버 측 렌더링과 클라이언트 측 렌더링을 올바르게 적용하는 방법을 놓고 오랫동안 논쟁이 벌어지고 있지만, 일부 페이지에는 서버 측 렌더링을 사용하도록 선택할 수 있고, 다른 페이지에는 서버 측 렌더링을 사용할 수 없다는 점을 기억해야 합니다. 일부 사이트에서는 하이브리드 렌더링 기술을 채택하여 성공을 거두었습니다. Netflix는 상대적으로 정적인 방문 페이지를 렌더링하지만, 상호작용이 많은 페이지의 경우 JS를 미리 가져오기하여 고부하 클라이언트 렌더링 페이지가 빠르게 로드될 가능성이 높습니다.

많은 최신 프레임워크, 라이브러리, 아키텍처를 통해 클라이언트와 서버 모두에서 동일한 애플리케이션을 렌더링할 수 있습니다. 이러한 기술은 서버 측 렌더링에 사용할 수 있습니다. 그러나 렌더링이 서버 클라이언트 모두에서 발생하는 아키텍처는 성능 특성과 절충사항이 매우 다른 자체 솔루션 클래스라는 점에 유의해야 합니다. React 사용자는 서버 DOM API 또는 이를 기반으로 구축된 솔루션(예: 서버 측 렌더링에 Next.js)을 사용할 수 있습니다. Vue 사용자는 Vue의 서버 측 렌더링 가이드 또는 Nuxt를 참고하세요. Angular는 범용입니다. 하지만 대부분의 인기 솔루션은 일정한 형태로 수분을 공급하므로 도구를 선택하기 전에 사용 중인 접근 방식을 숙지해야 합니다.

정적 렌더링

정적 렌더링은 빌드 시간에 발생합니다. 이 접근 방식은 클라이언트 측 JS의 양이 제한적이라는 가정 하에 빠른 FCP와 더 낮은 TBT 및 INP를 제공합니다. 또한 서버 측 렌더링과 달리, 페이지의 HTML이 서버에서 동적으로 생성되지 않아도 되므로 지속적으로 빠른 TTFB를 달성할 수 있습니다. 일반적으로 정적 렌더링이란 각 URL에 대해 별도의 HTML 파일을 미리 생성하는 것을 의미합니다. 미리 생성된 HTML 응답을 사용하면 정적 렌더를 여러 CDN에 배포하여 에지 캐싱을 활용할 수 있습니다.

FCP와 TTI에 영향을 미치는 정적 렌더링 및 선택적 JS 실행을 보여주는 다이어그램

정적 렌더링 솔루션은 다양한 모양과 크기로 제공됩니다. Gatsby와 같은 도구는 개발자에게 애플리케이션이 빌드 단계로 생성되는 것이 아니라 동적으로 렌더링되는 듯한 느낌을 주도록 설계되었습니다. 11ty, Jekyll, Metalsmith와 같은 정적 사이트 생성 도구는 정적인 특성을 수용하여 보다 템플릿 중심적인 접근 방식을 제공합니다.

정적 렌더링의 단점 중 하나는 가능한 모든 URL에 대해 개별 HTML 파일을 생성해야 한다는 것입니다. 이러한 URL을 미리 예측할 수 없거나 고유 페이지가 많은 사이트의 경우 이는 까다로울 수 있으며 실현 불가능할 수도 있습니다.

React 사용자는 Gatsby, Next.js 정적 내보내기 또는 Navi에 익숙할 수 있습니다. 이러한 모든 기능을 통해 구성요소를 사용하여 페이지를 편리하게 작성할 수 있습니다. 하지만 정적 렌더링과 사전 렌더링의 차이를 이해하는 것이 중요합니다. 정적 렌더링된 페이지는 많은 클라이언트 측 JavaScript를 실행할 필요 없이 상호작용이 가능한 반면, 사전 렌더링은 페이지가 진정한 대화형이 되기 위해 클라이언트에서 부팅해야 하는 단일 페이지 애플리케이션의 FCP를 개선합니다.

솔루션이 정적 렌더링인지 사전 렌더링인지 확실하지 않으면 자바스크립트를 사용 중지하고 테스트할 페이지를 로드해 보세요. 정적으로 렌더링된 페이지의 경우 JavaScript가 사용 설정되지 않아도 대부분의 기능이 존재합니다. 사전 렌더링된 페이지의 경우에도 링크와 같은 몇 가지 기본 기능이 있을 수 있지만 대부분의 페이지는 비활성입니다.

또 다른 유용한 테스트는 Chrome DevTools의 네트워크 제한을 사용하고 페이지가 대화형이 되기 전에 다운로드된 자바스크립트의 양을 관찰하는 것입니다. 일반적으로 사전 렌더링에서 상호작용이 가능하려면 더 많은 자바스크립트가 필요하며, JavaScript는 정적 렌더링에 사용되는 점진적 개선 방식보다 더 복잡한 경향이 있습니다.

서버 측 렌더링과 정적 렌더링 비교

서버 측 렌더링은 만병통치약이 아니며 동적인 특성으로 인해 상당한 컴퓨팅 오버헤드 비용이 수반될 수 있습니다. 많은 서버 측 렌더링 솔루션이 조기에 플러시되지 않거나, TTFB를 지연시키거나, 전송 중인 데이터를 두 배로 늘릴 수 있습니다 (예: 클라이언트에서 JavaScript가 사용하는 인라인 상태). React에서 renderToString()는 동기식 및 단일 스레드이므로 느릴 수 있습니다. 스트리밍을 지원하는 최신 React 서버 DOM API - HTML 응답의 초기 부분을 브라우저에 더 빨리 가져오고 나머지 부분은 서버에서 계속 생성할 수 있습니다.

서버 측 렌더링을 '올바르게' 하려면 구성요소 캐싱 솔루션을 찾거나 빌드하고, 메모리 소비를 관리하고, 메모화 기법을 적용하는 등의 문제가 있을 수 있습니다. 일반적으로 동일한 애플리케이션을 클라이언트에서 한 번, 서버에서 한 번, 여러 번 처리/재빌드하게 됩니다. 서버 측 렌더링이 무언가를 더 빨리 나타날 수 있다고 해서 갑자기 해야 할 작업이 줄어드는 것은 아닙니다. 서버에서 생성된 HTML 응답이 클라이언트에 도착한 후에 클라이언트에 많은 작업이 있는 경우 웹사이트의 TBT 및 INP가 높아질 수 있습니다.

서버 측 렌더링은 각 URL에 대해 주문형 HTML을 생성하지만 정적 렌더링된 콘텐츠를 제공하는 것보다 느릴 수 있습니다. 추가 작업을 할 수 있다면 서버 측 렌더링과 HTML 캐싱을 함께 사용하여 서버 렌더링 시간을 크게 줄일 수 있습니다. 서버 측 렌더링의 장점은 정적 렌더링을 사용할 때보다 더 많은 '실시간' 데이터를 가져오고 더 완벽한 요청에 응답할 수 있다는 점입니다. 맞춤설정이 필요한 페이지는 정적 렌더링에 적합하지 않은 요청 유형의 구체적인 예입니다.

서버 측 렌더링은 PWA를 빌드할 때 흥미로운 결정을 내릴 수도 있습니다. 전체 페이지 서비스 워커 캐싱을 사용하는 것과 개별 콘텐츠만 서버 렌더링하는 것 중 어느 것을 사용하는 것이 더 좋은가요?

클라이언트 측 렌더링

클라이언트 측 렌더링이란 브라우저에서 자바스크립트로 페이지를 직접 렌더링한다는 의미입니다. 모든 로직, 데이터 가져오기, 템플릿 작성, 라우팅은 서버가 아닌 클라이언트에서 처리됩니다. 결과적으로 더 많은 데이터가 서버에서 사용자 기기로 전달되고, 여기에는 단점도 있습니다.

휴대기기에서 클라이언트 측 렌더링을 가져오고 빠른 속도를 유지하기가 어려울 수 있습니다. 최소한의 작업이 이루어진 경우 클라이언트 측 렌더링은 순수한 서버 측 렌더링의 성능에 접근하여 충분한 자바스크립트 예산을 유지하고 가능한 한 적은 왕복으로 가치를 제공할 수 있습니다. <link rel=preload>를 사용하면 중요한 스크립트와 데이터를 더 빨리 제공할 수 있으므로 파서가 더 빨리 작동합니다. PRPL과 같은 패턴도 초기 탐색과 후속 탐색이 즉각적으로 느껴지도록 평가하는 것이 좋습니다.

FCP 및 TTI에 영향을 미치는 클라이언트 측 렌더링을 보여주는 다이어그램

클라이언트 측 렌더링의 가장 큰 단점은 애플리케이션이 커질수록 필요한 JavaScript의 양이 증가하는 경향이 있어 페이지의 INP에 부정적인 영향을 미칠 수 있다는 점입니다. 이는 처리 능력을 두고 경쟁하고, 종종 페이지의 콘텐츠를 렌더링하기 전에 처리해야 하는 새로운 JavaScript 라이브러리, 폴리필 및 타사 코드의 추가로 인해 특히 더 어려워집니다.

대규모 JavaScript 번들을 사용하는 클라이언트 측 렌더링을 사용하는 환경에서는 페이지 로드 중에 TBT 및 INP를 낮추기 위해 공격적인 코드 분할을 고려해야 하며, JavaScript를 지연 로드해야 합니다. 즉, '필요할 때 필요한 항목만 게재'해야 합니다. 상호작용이 거의 없거나 전혀 없는 환경의 경우 서버 측 렌더링이 이러한 문제에 대한 보다 확장 가능한 해결책이 될 수 있습니다.

단일 페이지 애플리케이션을 빌드하는 경우 대부분의 페이지에서 공유하는 사용자 인터페이스의 핵심 부분을 파악할 수 있으므로 애플리케이션 셸 캐싱 기법을 적용할 수 있습니다. 서비스 워커와 함께 사용하면 애플리케이션 셸 HTML과 종속 항목이 CacheStorage에서 매우 빠르게 로드될 수 있으므로 반복 방문 시 인지되는 성능을 크게 개선할 수 있습니다.

리하이드레이션을 통한 서버 측 렌더링과 클라이언트 측 렌더링 결합

이 접근 방식은 클라이언트 측 렌더링과 서버 측 렌더링을 모두 실행하여 클라이언트 측 렌더링과 서버 측 렌더링 간의 장단점을 완화하려고 시도합니다. 전체 페이지 로드 또는 새로고침과 같은 탐색 요청은 애플리케이션을 HTML로 렌더링하는 서버에서 처리되며, 그러면 렌더링에 사용되는 자바스크립트 및 데이터가 결과 문서에 삽입됩니다. 신중히 수행하면 서버 측 렌더링과 마찬가지로 빠른 FCP를 달성한 다음 (리)하이드레이션이라는 기술을 사용하여 클라이언트에서 다시 렌더링하여 '선호'합니다. 이는 효과적인 솔루션이지만 상당한 성능 단점이 수반될 수 있습니다.

리하이드레이션을 사용한 서버 측 렌더링의 가장 큰 단점은 FCP가 개선되더라도 TBT 및 INP에 상당히 부정적인 영향을 미칠 수 있다는 점입니다. 서버 측에서 렌더링된 페이지는 로드되고 대화형인 것처럼 보일 수 있지만, 구성요소의 클라이언트 측 스크립트가 실행되고 이벤트 핸들러가 연결될 때까지 실제로 입력에 응답할 수 없습니다. 모바일에서는 몇 초 또는 몇 분이 걸릴 수 있습니다.

이런 문제를 직접 경험해 보셨을 것입니다. 페이지가 로드된 것처럼 보이더라도 한동안은 클릭하거나 탭해도 아무런 변화가 없습니다. 이 경우 사용자가 페이지와 상호작용하려고 할 때 아무 일도 일어나지 않는 이유를 궁금해할 수 있으므로 금방 짜증이 됩니다.

수분 부족 문제: 하나의 앱을 두 개 가격에 사용한 경우

리하이드레이션 문제는 자바스크립트로 인한 지연된 상호작용보다 심각한 경우가 많습니다. 서버에서 HTML을 렌더링하는 데 사용한 모든 데이터를 다시 요청할 필요 없이 클라이언트 측 JavaScript가 서버가 중단한 부분부터 정확하게 '시작'할 수 있도록 하기 위해, 현재 서버 측 렌더링 솔루션은 일반적으로 UI의 데이터 종속 항목의 응답을 스크립트 태그로 문서에 직렬화합니다. 결과 HTML 문서에는 높은 수준의 중복이 포함되어 있습니다.

직렬화된 UI, 인라인 데이터, bundle.js 스크립트가 포함된 HTML 문서

보시다시피 서버는 탐색 요청에 대한 응답으로 애플리케이션의 UI 설명을 반환하지만 UI를 작성하는 데 사용된 소스 데이터와 UI 구현의 전체 사본도 반환합니다. 그러면 클라이언트에서 부팅됩니다. bundle.js가 로드 및 실행을 완료한 후에만 이 UI가 대화형이 됩니다.

실제 웹사이트에서 서버 측 렌더링과 리하이드레이션을 사용하여 수집된 성능 측정항목은 사용하지 않는 것이 좋다는 것을 보여줍니다. 궁극적으로 그 이유는 사용자 환경에 있습니다. 즉, 사용자가 준비가 된 것처럼 보여도 상호작용이 없는 것처럼 느껴지는 '알 수 없는 골짜기'에 빠지기 쉽습니다.

TTI에 부정적인 영향을 미치는 클라이언트 렌더링을 보여주는 다이어그램

하지만 리하이드레이션을 사용한 서버 측 렌더링도 가능합니다. 단기적으로, 캐시하기 쉬운 콘텐츠에만 서버 측 렌더링을 사용하면 TTFB를 줄일 수 있으므로 사전 렌더링과 유사한 결과가 생성됩니다. 향후 이 기법의 실행 가능성을 높이기 위해서는 증분, 점진적 또는 부분적으로 수분 공급이 핵심이 될 수 있습니다.

스트리밍 서버 측 렌더링 및 점진적 리하이드레이션

서버 측 렌더링은 지난 몇 년 동안 많은 발전이 있었습니다.

스트리밍 서버 측 렌더링을 사용하면 HTML을 청크 단위로 전송할 수 있으며, 수신 시 브라우저가 점진적으로 렌더링할 수 있습니다. 이로 인해 마크업이 사용자에게 더 빨리 도착하므로 FCP가 빨라질 수 있습니다. React에서 스트림은 동기식 renderToString()와 달리 [renderToPipeableStream()]에서 비동기식이므로 백프레셔가 잘 처리됩니다.

점진적인 수분 보충도 고려할 가치가 있으며 React가 달성한 것이 있습니다. 이 접근 방식을 사용하면 전체 애플리케이션을 한 번에 초기화하는 현재의 일반적인 접근 방식이 아니라 시간이 지남에 따라 서버 렌더링 애플리케이션의 개별 부분이 '부팅'됩니다. 이렇게 하면 페이지에서 우선순위가 낮은 부분의 클라이언트 측 업그레이드를 지연시켜 기본 스레드가 차단되는 것을 방지할 수 있으므로 페이지를 대화형으로 만드는 데 필요한 자바스크립트의 양을 줄이는 데 도움이 될 수 있습니다. 이를 통해 기본 스레드가 차단되는 것을 막을 수 있기 때문입니다.

점진적 리하이드레이션은 또한 서버에서 렌더링된 DOM 트리가 소멸된 후 즉시 다시 빌드되는 가장 일반적인 서버 측 렌더링 리하이드 함정을 피하는 데 도움이 될 수 있습니다. 대부분의 경우 초기 동기식 클라이언트 측 렌더링에는 아직 준비되지 않은 데이터, 즉 Promise의 해결을 기다리고 있기 때문입니다.

부분 수분 보충

부분적인 수분 보충은 구현하기가 어려운 것으로 입증되었습니다. 이 접근 방식은 점진적으로 수화될 개별 조각 (구성요소/뷰/트리)을 분석하고 상호작용이 거의 없거나 반응이 없는 조각을 식별하는 점진적 리수화 개념의 확장입니다. 대부분의 정적인 각 부분에 대해 상응하는 JavaScript 코드는 비활성 참조 및 장식 기능으로 변환되어 클라이언트 측 공간을 거의 0으로 줄입니다.

부분 하이드레이션 접근 방식에는 자체적인 문제와 절충이 수반됩니다. 이는 캐싱에 몇 가지 흥미로운 문제를 제기하며, 클라이언트 측 탐색은 애플리케이션의 비활성 부분에 대한 서버 렌더링 HTML을 전체 페이지 로드 없이 사용할 수 있다고 가정할 수 없음을 의미합니다.

삼형 렌더링

서비스 워커를 사용할 수 있다면 '삼형' 렌더링도 도움이 될 수 있습니다. 이는 초기/JS가 아닌 탐색에 스트리밍 서버 측 렌더링을 사용하고, 설치 후 서비스 워커가 탐색용 HTML 렌더링을 맡도록 할 수 있는 기법입니다. 이렇게 하면 캐시된 구성요소와 템플릿을 최신 상태로 유지하고 동일한 세션에서 새 뷰를 렌더링하는 SPA 스타일의 탐색을 사용할 수 있습니다. 이 접근 방식은 서버, 클라이언트 페이지 및 서비스 워커 간에 동일한 템플릿 및 라우팅 코드를 공유할 수 있는 경우에 가장 효과적입니다.

서버와 통신하는 브라우저 및 서비스 워커를 보여주는 삼형 렌더링 다이어그램

검색엔진 최적화 고려사항

팀에서 웹 렌더링 전략을 선택할 때 검색엔진 최적화의 영향을 고려하는 경우가 많습니다. 서버 측 렌더링은 크롤러가 쉽게 해석할 수 있는 '완전한' 환경을 제공하기 위해 선택됩니다. 크롤러는 자바스크립트를 이해할 수 있지만, 렌더링 방식에 주의해야 할 제한사항이 있는 경우가 많습니다. 클라이언트 측 렌더링은 작동할 수 있지만, 추가 테스트와 사전 작업이 있어야 작동하는 경우가 많습니다. 최근에는 아키텍처가 클라이언트 측 JavaScript에 크게 의존하는 경우 동적 렌더링도 고려할 만한 옵션이 되었습니다.

확실하지 않은 경우 선택한 방식이 제대로 작동하는지 테스트하는 데 모바일 친화성 테스트 도구를 사용하면 좋습니다. 이 도구는 페이지가 Google 크롤러에 어떻게 표시되는지, 자바스크립트가 실행된 후 발견된 직렬화된 HTML 콘텐츠, 렌더링 중에 발생한 오류를 시각적으로 보여줍니다.

모바일 친화성 테스트 UI의 스크린샷

요약

렌더링 방식을 결정할 때는 병목 현상이 무엇인지 측정하고 파악하세요. 정적 렌더링과 서버 측 렌더링 중 어느 것을 사용해야 이러한 목표를 달성할 수 있는지 고려하세요. 대화형 환경을 구현하기 위해 최소한의 JavaScript로 HTML을 대부분 제공하는 것은 괜찮습니다. 다음은 서버-클라이언트 스펙트럼을 보여주는 유용한 인포그래픽입니다.

이 도움말에 설명된 다양한 옵션을 보여주는 인포그래픽입니다.

크레딧

리뷰와 아이디어를 주신 모든 분들께 감사드립니다.

Jeffrey Posnick, Houssein Djirdeh, Shubhie Panicker, Chris Harrelson, Sebastian Markbåge