서버 사이드 렌더링

강연주·2024년 10월 19일

📚 TIL

목록 보기
71/186

서버 사이드 렌더링

과거 리액트 애플리케이션을 만들 때 Create React App(create-react-app)이 각광받았지만, 요즘은 서버 사이드 렌더링을 지원하는 Next.js 같은 프레임워크를 사용해 프로젝트를 만드는 것이 인기다.
이에 따라, 리액트 개발자의 서버 사이드 렌더링에 대한 이해도도 중요해지고 있다.


🪂 서버 사이드 렌더링이란?

코드를 작성하다 보면 느끼겠지만, 서버 사이드 렌더링 코드를 작성하는 데는 싱글 페이지 애플리케이션을 만드는 것보다 신경 쓸 점이 훨씬 많다. 왜 서버 사이드 렌더링이 필요한지 배우지 못한다면 일련의 작업들이 귀찮게만 느껴질 수 있다. 현재 웹 상황과, 왜 서버 사이드 렌더링을 알아둬야 하는지 먼저 살펴본다.


⚓ 싱글 페이지 애플리케이션의 세상

🟪 싱글 페이지 애플리케이션(Single Page Application)이란?

렌더링과 라우팅에 필요한 대부분의 기능을 서버가 아닌 브라우저의 자바스크립트에 의존하는 방식. 최초에 첫 페이지에서 데이터를 모두 불러온 이후에는, 페이지 전환을 위한 모든 작업이 자바스크립트와 브라우저의 history.pushState와 history.replaceState로 이뤄지기 때문에, 페이지를 불러온 이후에는 서버에서 HTML을 내려받지 않고 하나의 페이지에서 모든 작업을 처리한다.

예시로, 사이트 렌더링에 필요한 내부의 내용을 모두 자바스크립트 코드로 삽입한 이후에 렌더링하기 때문에, 소스 보기로 HTML 코드를 봤을 때는 <body/> 내부에 아무런 내용이 없다.

또 페이지 전환 시에도 새로운 HTML 페이지를 요청하는 게 아니라, 자바스크립트에서 다음 페이지의 렌더링에 필요한 정보만 HTTP 요청 등으로 가져온 다음, 그 결과를 바탕으로 <body/> 내부에 DOM을 추가, 수정, 삭제하는 방법으로 페이지가 전환된다.

즉, 최초에 서버에서 최소한의 데이터를 불러온 이후부터는, 이미 가지고 있는 자바스크립트 리소스와 브라우저 API를 기반으로 모든 작동이 이루어진다. 이러한 작동 방식은 최초에 로딩해야 할 자바스크립트 리소스가 커지는 단점이 있지만, 한 번 로딩된 이후에는 서버를 거쳐 필요한 리소스를 받아올 일이 적어지기 때문에, 사용자에게 훌륭한 UI/UX를 제공한다는 장점이 있다.

🟪 전통적 방식의 애플리케이션과 싱글 페이지 애플리케이션의 작동 비교

과거 서버 사이드에서 작동하던 전통적인 방식의 애플리케이션을 상상해 보자. 페이지 전환이 발생할 때마다 새롭게 페이지를 요청하고, HTML 페이지를 다운로드해 파싱하는 작업을 거친다. 이 과정은 페이지를 처음부터 새로 그려야 해서 일부 사용자는 페이지가 전환될 때 부자연스러운 모습을 보게 된다.

예시로 네이버가 있는데, 네이버 스포츠 화면은 네이버 홈과 다른 환경에서 HTML을 만들어서 제공하므로 어쩔 수 없이 처음부터 HTML을 다시 완성한다. 이 경우, 페이지를 처음부터 다시 다운로드받고 렌더링하는 작업을 거쳐야 하며, 사용자 입장에서는 페이지가 처음부터 다시 만들어지는 것처럼 보인다.

그러나 이런 페이지 전환을 모두 자바스크립트로 한다면, 최초에 한 번 모든 리소스를 다운로드한 후 페이지를 전환할 때 추가로 리소르를 다운로드하는 시간이 필요없다. 그리고 경우에 따라 페이지 전체를 새로 렌더링하는 것이 아니라 페이지 전환에 필요한 일부 영역만 다시 그리게 되므로 훨씬 더 매끄러운 UI를 보여줄 수 있다.

Gmail에서는, 최초에 한 번 다소간의 로딩이 끝난 이후에는 페이지 전환이 모두 자바스크립트로 이루어진다. 예를 들어, 이메일 목록에서 이메일 메시지를 클릭하면 주소는 변경되지만 목록만 특정 메일로 매끄럽게 전환된다.

🟪 싱글 페이지 렌더링 방식의 유행과 JAM 스택의 등장

상기 장점을 발판으로 근래 많은 웹페이지들이 싱글 페이지 렌더링 방식을 채택했다. 그렇다면 이런 방식은 어떻게 등장하게 된 것일까?

과거 PHP나 JSP(JavaServer Pages)를 기반으로 대부분의 웹 애플리케이션이 만들어졌을 때는 거의 대부분의 렌더링이 서버 사이드에서 이뤄졌다. 페이지를 요청하면 서버에서 완성돈 HTML을 내려받고, 또 페이지 이동이 있으면 새로운 페이지를 서버에서 내려받는 방식이었다. 여기서 자바스크립트는 어디까지나 사용자에게 추가적인 경험을 주기 위한 보조적인 수단으로 사용됐다.

그러나 자바스크립트가 서서히 다양한 작업을 수행하게 되면서, 자바스크립트를 모듈화하는 방안이 점차 논의되기 시작했고, 그에 따라 등장한 것이 CommonJS와 AMD(Asynchronous Module Definition)다. 이러한 자바스크립트 모듈화의 결실, 사용자 기기의 성능 향상과 인터넷 속도의 발전 등으로 자바스크립트에서 할 수 있는 일이 점점 다양해졌다.

❓모듈화? CommonJS? AMD?

이런 변화에 힘입어 2010년경 Backbone.js와 AngularJS, Knockout.js 등이 등장하면서 자바스크립트 수준에서 MVx 프레임워크를 구현하기 시작했다. 이는 자바스크립트에서도 어느 정도 서버에서만 할 수 있는 복잡한 작업을 할 수 있다는 것을 의미했고, 자바스크립트의 역할과 규모는 커져갔다.

프레임워크의 인기는 자바스크립트의 역할을 더욱 가중시켰고, 이후 React, Vue, Angular의 시대가 오게 된다. 오늘날 많은 웹 애플리케이션이 자바스크립트를 빼고는 제대로 페이지를 보여줄 수 없을 정도로 그 역할에 크게 의존하고 있으며, 자바스크립트 개발자들은 웹페이지의 모든 영역(페이지의 렌더링에서부터 사용자 인터렉션까지)을 담당하면서, 이를 모두 아우를 수 있는 싱글 페이지 렌더링 방식이 인기를 얻게 됐다.

싱글 페이지 애플리케이션, 즉 클라이언트 사이드 라우팅(❓렌더링이 아니고?)이 널리 퍼지게 된 이유는 향상된 UX 제공뿐은 아니다. PHP 시절에 웹 애플리케이션을 만들기 위해서는 JS 외에도 신경 쓸 것이 많았지만, 싱글 페이지 애플리케이션에서는 단지 브라우저 내부에서 작동하는 자바스크립트만 잘 작성하면 문제가 없다. 즉, 프런트엔드 개발자들에게 좀 더 간편한 개발 경험을 제공했는 장점이 있다.

싱글 페이지 애플리케이션의 유행으로 새로 생겨난 용어가 있으니, 바로 JAM 스택이다. 기존 웹 개발은 LAMP 스택, 즉 Linux(운영 체제), Apache(서버), MySQL(데이터베이스) PHP/Python(웹 프레임워크) 등으로 구성돼 있었다. 이 LAMP 스택은 과거 인기 있는 구조이기도 했던 동시에 어쩔 수 없는 선택이기도 했다. 그러나 이런 서버 의존성은 웹 애플리케이션의 확장성에도 걸림돌로 작용했는데, 웹 애플리케이션의 기능이 다양해지거나 사용자가 늘어나면 이와 동시에 서버도 확장해야 했기 때문이다. 클라우드의 개념이 부족했던 당시에는 서버 확장이 매우 번거로웠다.

하지만 앞서 언급한 프레임워크의 등장으로 JAM(JavaScript, API, Markup) 스택이 등장한다. 대부분의 작업을 자바스크립트에서 수행할 수 있기 때문에, 프런트엔드는 JS와 마크업(HTML,CSS)을 미리 빌드해 두고 저적으로 사용자에게 제공하면, 이후 작동은 모두 사용자의 클라이언트에서 실행되므로 서버 확장성 문제에서 보다 자유로워질 수 있게 됐다. JAM 스택의 인기와 Node.js의 고도화 덕분에, MEAN(MongoDB, Express.js, AngularJS, Node.js)이나 MERN(MongoDB, Express.js, React, Node.js)스택처럼 아예 API 서버 자체도 자바스크립트로 구현하는 구조가 인기를 끌기 시작했다.

🟪 새로운 패러다임의 웹서비스를 향한 요구

많은 양의 리소스가 자바스크립트로 넘어오기 시작하고 자바스크립트 코드의 규모도 커지면서, 이에 대한 우려의 목소리도 나오기 시작했다. 다만 이 시기에 있었던 폭발적 기술 발전으로 문제가 자연스럽게 해결될 것이라 기대하기도 했었다. 인터넷 속도는 날이 갈수록 빨라졌고, 프로세서나 메모리 등 하드웨어 성능 또한 눈부신 발전을 거듭했기 때문이다.

즉, 웹페에지를 불러오는 데 필요한 부담 일정 부분 사용자에게 전가하더라도, 사용자의 기기나 인터넷 환경이 더 빠르게 발전할 것이기 때문에 괜찮을 것이라는 기대감이 팽배했다. 이에 부응이라도 하듯 웹 애플리케이션에서 제공하는 자바스크립트 리소스의 크기와 모두 증가하기 시작했다.

그러면 웹페이지 속도는 얼마나 개선됐을까? 사용자의 디바이스가 크기도 커지고 개수도 더 많아진 자바스크립트 리소스를 충분히 효과적으로 처리하고 있을까?

모바일 기기의 성능은 날이 갈수록 좋아지고 있지만, 예시의 모바일에서 웹페이지 로딩은 여전히 20초 가까이 걸린다는 것을 알 수 있다.

사용자가 페이지에서 최초로 인터렉션이 가능해지기까지 걸리는 시간을 보면, 모바일에서 사용자가 어떤 액션을 취하고 싶다면 약 15초 이상을 대기해야 함을 알 수 있다.

눈에 띄는 점은, 자바스크립트 파싱을 위해 CPU를 소비하는 시간이 눈에 띄게 증가한 것이다. 그만큼 자바스크립트에서 처리해야 하는 코드의 절대량이 증가했음을 의미한다. 코드 양이 증가해도 처리 시간이 줄어들었다면 괜찮겠지만, 사실 그렇지도 못하다. 모바일에서 사용자가 상호작용 가능할 때까지 대기해야 하는 평균 시간은 12초, 모든 콘텐츠 로딩에 소요되는 시간은 약 18초다.

물론 이 모든 것이 싱글 페이지 애플리케이션의 탓은 아니며, 현재의 웹 애플리케이션이 처리하는 다양한 작업과, 하이브리드 애플리케이션의 형태로 앱 내부에서도 웹처럼 구동되는 경우들에서도 기인한다.
중요한 사실은, 웹 전반의 환경이 대폭 개선됐음에도, 실제 사용자들이 느끼는 로딩 속도는 5년전과 크게 차이가 없거나 오히려 더 느리다는 것이다.


⚓ 서버 사이드 렌더링이란?

싱글 페이지 애플리케이션이 자바스크립트를 활용해 하나의 페이지에서만 렌더링을 수행한다면, 서버 사이드 렌더링은 최초에 사용자에게 보여줄 페이지를 서버에서 렌더링해 빠르게 화면을 제공하는 방식이다.

웹페이지가 점점 느려지는 문제를, 싱글 페이지 애플리케이션의 태생적 한계에서 찾고, 이를 개선하고자 서버에서 페이지를 렌더링해 제공하는 기존 방식의 웹 개발이 다시금 떠오르고 있다.

  • MPA vs SPA

https://lvivity.com/single-page-app-vs-multi-page-app

멀티 페이지 애플리케이션은 모든 페이지를 서버에서 요청받은 후에
완성된 HTML을 렌더링한다.

➡️ 둘의 차이점은 결국 웹페이지 렌더링의 책임을 어디에 두느냐다.

싱글 페이지 애플리케이션은 사용자에게 제공되는
자바스크립트 번들에서 렌더링을 담당하지만,

서버 사이드 방식렌더링에 필요한 작업을 모두 서버에서 수행한다.

클라이언트의 렌더링은 사용자 기기 성능에 영향을 받지만, 서버 사이드 렌더링은 서버에서 제공하므로 비교적 안정적이다.


🎭 서버 사이드 렌더링의 장점

1. 최초 페이지 진입이 비교적 빠르다.

사용자가 최초 페이지에 진입했을 때 페이지에 유의미한 정보가 그려지는 시간(First Connectful Paint)이 더 빨라질 수 있다. 최초에 사용자에게 보여줘야 할 화면에 표시할 정보가, 외부 API 호출에 많이 의지한다고 가정해 보자. 싱글 페이지 애플리케이션이라면 사용자가 해당 페이지에 진입하고, 자바스크립트 리소스를 다운로드하고, HTTP 요청을 수행한 이후에 이 응답의 결과를 가지고 화면을 렌더링해야 할 것이다.

그러나 이러한 작업이 서버에서 이뤄진다면 한결 빠르게 렌더링될 수 있다. 일반적으로 서버에서 HTTP 요청을 수행하는 것이 더 빠르고, 또 HTML을 그리는 작업도 서버에서 해당 HTML을 문자열로 미리 그려서 내려주는 것이, 클라이언트에서 기존 HTML에 삽입하는 것보다 더 빠르기 때문이다.

모든 경우에 서버 사이드 렌더링이 초기 페이지 렌더링에 비해 이점을 가진다고 볼 수는 없지만, 화면 렌더링이 HTTP 요청에 의존적이거나, 렌더링해야 할 HTML의 크기가 커진다면 상대적으로 서버 사이드 렌더링이 더 빠를 수 있다.

물론 이것은 서버가 사용자에게 렌더링을 제공할 수 있을 정도의 충분한 리소스가 확보돼 있다는 일반적 가정하에 비교한 것이다. 서버가 사용자를 감당하지 못하고, 리소스를 확보하기 어렵다면 오히려 싱글 페이지 애플리케이션보다 느려질 수도 있다.

2. 검색 엔진과 SNS 공유 등 메타데이터 제공이 쉽다.

서버 사이드 렌더링은 검색 엔진 최적화에 유용하다.

  • 검색 엔진이 사이트에서 필요한 정보를 가져가는 과정
  1. 검색 엔진 로봇(머신)이 페이지에 진입한다.
  2. 페이지가 HTML 정보를 제공해, 로봇이 이 HTML을 다운로드한다.
    단, 다운로드만 하고 자바스크립트 코드는 실행하지 않는다.
  3. 다운로드한 HTML 페이지 내부의 오픈 그래프(Open Graph)나,
    메타(meta) 태그 정보를 기반으로 페이지의 검색(공유) 정보를 가져오고 이를 바탕으로 검색 엔진에 저장한다.
    ❓오픈 그래프?

이 검색 엔진의 페이지 방문과 사용자의 브라우저를 이용한 페이지 방문의 큰 차이점은, 페이지 내부에 있는 JS 코드의 실행 여부다. 브라우저는 해당 페이지를 사용자에게 HTML이나 각종 정보로 제공하기 위해 자바스크립트를 실행해야 하지만, 로봇은 페이지를 보는 것이 아닌 페이지의 정적인 정보를 가져오는 것이 목적이므로, 자바스크립트를 다운로드하거나 실행할 필요가 없다.

싱글 페이지 애플리케이션은 대부분의 작동이 자바스크립트에 의존하는데, 이러한 메타 정보 또한 마찬가지다. 검색 엔진이 최초에 방문했을 때, 즉 페이지에 최초로 진입했을 때 이러한 메타 정보를 제공할 수 있도록 조치를 취하지 않는다면, 검색 엔진이나 SNS 공유 시에 불이익이 있을 수 있다.

반면 서버 사이드 렌더링은 최초의 렌더링 작업이 서버에서 일어난다. 즉, 검색 엔진에 제공할 정보를 서버에서 가공해서 HTML 응답으로 제공할 수 있으므로 검색 엔진 최적화에 대응하기 용이하다.

3. 누적 레이아웃 이동이 적다.

서버 사이드 렌더링은 누적 레이아웃 이동(Cumulative Layout Shift)을 줄일 수 있다. 누적 레이아웃 이동이란, 사용자에게 페이지를 보여준 이후에 뒤늦게 어떤 HTML 정보가 추가되거나 삭제되어 마치 화면이 덜컥거리는 것과 같은 부정적인 사용자 경험을 말한다. 즉, 사용자가 예상치 못한 시점에서 페이지가 변경되어 불편을 초래하는 것이다. 신문 기사를 제공하는 사이트를 예로 들어보자. 화면 전체에 기사내용이 있고, 중간에 가로로 긴 배너를 삽입하고자 한다. 그런데 기사(글)의 로딩은 빨리 이루어져 화면에 먼저 노출되고 있는데, 갑작스럽게 뒤늦게 배너가 로딩된다면 어떨까? 배너의 크기만큼 글 영역이 밀리면서 사용자에게 불편을 초래할 것이다.
❓CLS 문제

싱글 페이지 애플리케이션에서는 페이지 콘텐츠가 API 요청에 의존하고, API 요청의 응답 속도가 제각각이며, 이를 적절히 처리해두지 않는다면 이러한 누적 레이아웃 이동 문제가 발생할 수 있다.

반면, 서버 사이드 렌더링의 경우에는 이러한 요청이 완전히 완료된 이후에 완성된 페이지를 제공하므로, CLS 문제에서 비교적 자유롭다.

물론, 서버 사이드 렌더링을 사용한다 해도 CLS에서 완전히 자유로운 것은 아니다. 리액트를 예로 들어보자. useEffect는 클라이언트에서 컴포넌트가 마운트된 이후에 실행되므로, 서버 사이드 애플리케이션이나 싱글 페이지 애플리케이션에서 모두 문제의 소지가 있다. 또한 API 속도가 모두 달랐을 때, 서버 사이드 렌더링에서는 모든 요청이 완료되기 전까지 페이지가 렌더링되징 않을 것이므로, 최초 페이지 다운로드가 굉장히 느려질 수도 있다. (이는 리액트 18에서 등장한 스트림으로 해결될 수도 있다)
❓스트림?

4. 사용자의 디바이스 성능에서 비교적 자유롭다.

서버 사이드 렌더링은 비교적 사용자 디바이스 성능으로부터 자유롭다. 자바스크립트 리소스 실행은, 사용자의 디바이스에서만 실행되므로 절대적으로 그 성능에 의존한다. 그러나 서버 사이드 렌더링을 수행하면, 이런 부담을 서버에 나눌 수 있다.
(물론 이 또한 절대적인 것은 아니다. 인터넷 속도가 느리다면 어떤 방법이어도 느릴 것이고, 사용자 방문 폭증으로 서버 부담이 가중되고 이에 대응할 적절한 처리가 수반돼있지 않다면 서버 사이드 렌더링도 느려질 수 있다.)

5. 보안에 좀 더 안전하다.

JAM 스택을 채택한 프로젝트의 공통된 문제점은 애플리케이션의 모든 활동이 브라우저에 노출된다는 것이다. 브라우저의 개발자 도구를 사용하면 웹사이트에서 일어나는 거의 대부분의 작업을 파악할 수 있다. 이 작업에는 API 호출과 인증 같이 사용자에게 노출되면 안되는 민감한 작업도 포함되므로, 정상적 비즈니스 로직을 거치지 않은 상황에서 인증이나 API가 호출되는 것을 항상 방지할 준비가 돼있어야 한다. 반면, 서버 사이드 렌더링의 경우, 인증 혹은 민감한 작업을 서버에서 수행하고 그 결과만 브라우저에 제공해 보안 위협을 피할 수 있다.

🎭 서버 사이드 렌더링의 단점

1. 소스코드를 작성할 때 항상 서버를 고려해야 한다.

서버 사이드 렌더링 적용시에는, 소스코드 전반에 걸쳐 서버 환경에 대한 고려가 필요하다. 그 중 가장 큰 문제가 바로 브라우저 전역 객체인 window 또는 sessionStorage와 같이 브라우저에만 있는 전역 객체 등이다. 소스코드나 사용 중인 라이브러리에서 window를 사용하고 있고, 이 코드가 만약 서버에서 실행된다면 'window is not defined'라는 에러를 마주하게 된다. 그러므로 서버에서도 실행될 가능성이 있는 코드라면 window에 대한 접근을 최소화해야 하고, window 사용이 불가피하다면 해당 코드가 서버 사이드에서 실행되지 않도록 처리해야 한다. 이러한 서버에 대한 고려는 작성한 코드뿐만 아니라, 외부에서 의존하고 있는 라이브러리도 마찬가지다. 해당 라이브러리가 마찬가지로 서버에 대한 고려가 돼 있지 않다면, 다른 대안을 찾거나 클라이언트에서만 실행될 수 있도록 처리해야 한다. 잠재적인 위험을 가진 코드를 모두 클라이언트에서 실행하는 것 또한 궁극적 해결척이 아니다. 클라이언트에서만 실행되는 코드가 많아질수록, 서버 사이드 렌더링의 장점을 잃는 셈이다.
아 어쩌란 말이냐 트위스트 추면서

2. 적절한 서버가 구축돼 있어야 한다.

싱글 페이지 애플리케이션이나 정적인 HTML 페이지만으로 서비스할 수있는 웹페이지의 경우에는, 단순히 HTML과 자바스크립트, CSS 리소스를 다운로드할 수 있는 준비만 하면 된다. 서버는 정적인 데이터인 자바스크립트와 HTML을 제공하면 모든 역할이 끝난다.

그러나 서버 사이드 렌더링은 말 그대로 사용자의 요청을 받아 렌더링을 수행할 서버가 필요하고, 서버 구축은 절대 쉬운 일이 아니다. 사용자의 요청에 따라 적절히 대응할 수 있는 물리적 가용량을 확보해야 하고, 때로는 예기치 않은 장애 상황에 대응할 수 있도록 복구 전략도 필요하다. 또한, 요청을 분산시키고, 프로세스가 예기치 못하게 다운될 때를 대비해 PM2와 같은 프로세스 매니저의 도움도 필요하다. 실제로 프로덕션 서버 사이드 렌더링 애플리케이션을 운영해 본 경험이 있다면, 쿠버네티스 같은 여러가지 라이브러리와 도구의 도움을 얻는다 하더라도 쉽지 않은 일임을 알게 된다.

❓PM2? 프로세스 매니저?
❓ 쿠버네티스?

3. 서비스 지연에 따른 문제

만약 싱글 페이지 애플리케이션에서 무언가 느린 작업이 있다고 해보자. 싱글 페이지 애플리케이션은 그래도 최초에 어떤 화면이라도 보여준 상태에서 무언가 느린 작업이 수행되기 때문에, '로딩 중'과 같이 작업이 진행 중임을 적절히 안내할 수 있고, 사용자가 기다릴 여지가 있다.

반면 서버 사이드 렌더링에서 지연이 일어나면 어떻게 될까? 특히 이 지연 작업이 최초 렌더링에 발생한다면 큰 문제가 된다. 서버 사이드 렌더링은 서버에서 사용자에게 보여줄 페이지에 대한 렌더링 ㅈ가업이 끝나기까지는, 사용자에게 그 어떤 정보도 제공할 수 없다. 애플리케이션의 규모가 커지고, 작업이 복잡해지고, 이에 따라 다양한 요청이 얽혀 있어 병목현상이 심해진다면 서버 사이드 렌더링이 더 나쁜 사용자 경험을 제공할 수도 있다.


( 출처 : 모던 리액트 Deep Dive, 김용찬, 위키북스 )

profile
아무튼, 개발자

0개의 댓글