여기서 SPA란, Single Page Application의 약자로, 하나의 페이지로 구성된 웹 애플리케이션이다. SPA로 개발된 웹사이트에서는 카테고리에 있는 각 메뉴를 선택하면 보통 헤더는 고정되어 있는 상태로 메인화면 혹은 클릭한 부분만 바뀐다.
SPA는 웹 애플리케이션에 필요한 정적 리소스를 한꺼번에 모두 다운로드하고, 이후 새로운 페이지 요청이 왔을 때 필요한 데이터만 전달받아서 클라이언트에서 필요한 페이지를 갱신하기 때문에 CSR로 렌더링 한다.
반면 MPA란, Multi Page Application의 약자로, 탭을 이동할때마다 서버로부터 새로운 HTML을 새로 받아와서 페이지 전체를 렌더링 하는 전통적인 웹 페이지 구성 방식이다.
MPA는 새로운 요청이 있을 때마다 서버에서 이미 렌더링된 정적 리소스를 받아오기 때문에 SSR로 렌더링 한다.
CSR은 Client Side Rendering의 약자로, 클라이언트 측에서 렌더링 하는 방식이고, SSR은 Server Side Rendering의 약자로, 서버 측에서 렌더링 하는 방식이다.
말 그대로 어느 Side에서 렌더링을 준비하느냐에 따라 나뉘는 개념이다.
이 두 가지 개념을 공부할 때 자주 언급되는 SSG 라는 것도 있는데, SSG는 Static Site Generation의 약자로, Static Rendering 이라고도 불리는 방식이다. 서버에서 HTML을 보내준다는 차원에서는 SSR과 유사하나, '언제' 만들어주는지에 따라 다른 것이다.
SSR은 요청 시 서버에서 즉시 HTML을 만들어 응답하기에 데이터가 달라지거나 자주 바뀌어서 미리 만들어 두기 어려운 페이지에 적합하고, SSG는 애플리케이션을 빌드할 때 HTML을 생성하는 렌더링 방식이다. 페이지들을 서버에 모두 만들어 둔 후에 요청 시 해당 페이지를 응답하는 것이므로 바뀔 일이 거의 없어 캐싱해두면 좋을 페이지에 사용하면 적합하다. SSG로 생성된 HTML은 요청마다 재사용되며, CDN에서 캐시할 수도 있다.
Next.js의 렌더링 방식의 종류에 대해 이야기하면, Next.js에는 SSR을 포함하여 아래의 4가지 렌더링 방식이 있다.
여기서 4번을 주목하면 좋은데, CSR은 브라우저가
JS 파일을 다운로드 받고, 동적으로 DOM을 생성하는 시간을 기다려야 하기 때문에 초기 로딩 속도가 느리다는 것이 단점이다.
하지만 초기 로딩 이후에 페이지 일부를 변경할 때는 서버에서 해당 데이터만 요청하면 되기 때문에 이후 구동 속도는 빠르다는 특징이 있다.
서버는 빈 뼈대만 있는 HTML을 넘겨주는 역할만 수행하면 되기 때문에 서버 측의 부하가 적은데, 뿐만 아니라 클라이언트 측에서 연산, 라우팅 등을 모두 직접 처리하기 때문에 반응속도가 빠르고 UX도 우수하다는 장점이 있다.
한편, 브라우저들이 가지는 웹 크롤러는 HTML을 읽어 검색 가능한 색인을 만들어 내는데, 웹 크롤러 봇 입장에서는 HTML이 텅텅 비어 있는 것처럼 보여서 색인할만한 콘텐츠가 존재하지 않기에, SEO(검색엔진 최적화)에 불리하다는 단점이 있다.
열심히 서비스를 만들었는데 검색 사이트에 노출이 잘 되지 않는 슬픈 일이 있을 수도 있다는 것이다. 물론 구글의 크롤러 봇은 자바스크립트를 실행할 줄 알기에 CSR 웹 크롤링도 가능하다고 하나, 아직 완벽한 단계가 아니기에 구글 측에서도 여전히 SSR을 고려하라는 말을 덧붙이고 있다고 한다.
SSR은 모든 데이터가 이미 HTML에 담긴 채로 브라우저에 전달되기 때문에 SEO에 유리하다. 크롤러 봇이 HTML을 무리 없이 읽을 수 있기 때문이다. 더불어, 자바스크립트 코드를 다운로드 받고, 실행하기 전에 사용자가 이미 HTML이 렌더링 된 화면을 볼 수 있다. 이렇듯 JS 다운로드를 기다려야 했던 CSR 보다 초기 구동 속도가 빠를 수밖에 없다.
하지만 해당 시점에서는 사용자가 버튼을 클릭하거나 이동하려고 할 때 아무 반응이 없을 수 있다. interaction이 가능한 페이지처럼 보이지만 실제로는 내용과 스타일이 입혀진 껍데기에 불과하고 실제로는 클라이언트 측 JS가 실행되고 이벤트 핸들러가 첨부된 JS 로직이 모두 연결될 때까지 사용자의 입력에 응답할 수 없기 때문이다.
이렇듯, SSR에는 TTV(Time to View)와 TTI(Time to Interact)간의 시간 간격이 존재한다는 것이 단점이다. 반면에 CSR은 JS가 동적으로 DOM을 생성하기 때문에 HTML은 JS로직이 모두 완전히 연결된 상태라 사용자가 보는 시점과 이용할 수 있는 시점이 동일하다.
CSR | SSR | |
---|---|---|
장 점 | - 화면 깜빡임이 없음 - 초기 로딩 이후 구동 속도가 빠름 - TTV와 TTI 사이 간극이 없음 - 서버 부하 분산 | - 초기 구동 속도가 빠름 - SEO에 유리 |
단 점 | - 초기 로딩 속도가 느림 - SEO에 불리 | - 화면 깜빡임이 있음 - TTV와 TTI 사이 간극이 있음 - 서버 부하가 있음 |
CSR
1. 초기 로딩이후 구동속도가 빠르다.
2. 서버에서 불필요한 트래픽이 발생하지 않아 서버 운영 효율이 좋다.
3. 새로고침이 발생하지 않는다.
4. SEO에 분리하다.
SSR
1. SEO가 가능하다.
2. 화면 깜빢임이 있다.
3. 서버에서 HTML파일을 만들기 때문에 불필요한 트래픽이 발생한다.
SSG의 경우, 빌드를 다시 하지 않는 한 페이지를 업데이트할 수 없는 문제가 발생한다.
이를 해결하는 것이 ISR이라는 렌더링 방식이다.
ISR의 메커니즘은, 먼저, 페이지에 요청이 들어왔을 때 빌드 완료된 정적 페이지를 표시하면서, 백그라운드에서 재렌더링을 수행한다.
그리고, 재렌더링이 완료되면 즉시, 새로 생성한 페이지를 표시한다.
이것이 ISR의 메커니즘이다.
즉, SSG와 SSR의 장점을 모두 취할 수 있는 렌더링 방식이다.
SSG에 의한 빠른 응답을 실현하면서, 어느 정도의 데이터 일관성을 보장할 수 있다.
Next.js(Pages Router)에서는, getStaticProps의 반환 값에 revalidate라는 파라미터를 추가하여 실현할 수 있는데 revalidate에는 페이지의 재생성 간격을 지정해 준다.
예를 들어, revalidate: 60으로 설정하면, 60초마다 페이지를 재생성할 수 있다.
.
.
.
.
RSC란 컴포넌트가 렌더링되는 위치가 서버 측인지 클라이언트 측인지를 식별하는 기술이다.
여기서, 서버 측에서 렌더링되는 컴포넌트를 '서버 컴포넌트’라고 하고, 클라이언트 측에서 렌더링되는 컴포넌트를 '클라이언트 컴포넌트’라고 한다.
서버 컴포넌트는 처리 결과만을 브라우저에 전송 한다.
예를 들어, 날짜 처리를 위한 외부 라이브러리를 설치하여 사용하더라도, 그 라이브러리의 소스 코드 자체는 브라우저에 전송되지 않습니다.
이로 인해 브라우저에 전송하는 파일의 용량이 줄어들고, 성능이 향상됩니다.
RSC의 경우 서버와 클라이언트에서 각각의 컴포넌트가 렌더링된다.
SSR의 경우 클라이언트에 전송되는 JavaScript의 양이 많다.
이렇게 SSR과 RSC를 조합함으로써, 초기 표시를 빠르게 하면서, 클라이언트 측에 전송하는 JavaScript의 양을 줄일 수 있다.
React 18에서 서버 사이드 렌더링을 강화하기 위해 비동기 SSR이라는 새로운 기능이 추가되어 컴포넌트 단위로 SSR을 수행할 수 있게 되었다.
즉, 렌더링에 시간이 걸리는 컴포넌트를 서버 측에서 처리하는 도중에 화면을 표시하고, 또한 사용자는 그 화면을 조작할 수 있다.
이전의 React에서는 SSR을 수행하면 페이지 전체의 렌더링이 완료될 때까지 브라우저에 HTML을 전송할 수 없었다.
React 18에서는 Suspense라는 컴포넌트로 구분된 단위로 비동기로 SSR을 수행할수 있고
페이지 전체의 렌더링이 완료되지 않아도 브라우저 상에서 표시를 시작할 수 있게 되었다.
그리고 렌더링 중인 컴포넌트는 Suspense의 인수로 지정된 로딩을 표시하고, 렌더링이 완료되면 해당 컴포넌트의 화면을 표시 할수 있다.
이를 스트리밍 HTML이라고 한다.
또한, 비동기 SSR을 수행하면서, 먼저 로드되어 화면에 표시된 부분을 인터랙티브하게 조작할 수도 있게 되었다.
즉, 하이드레이션을 단계적으로 수행한다는 것이다.
이러한 단계적인 하이드레이션을 선택적 하이드레이션이라고 하며 React 18에서 실행할 수 있게 된 기능 중 하나이다.
use client 디렉티브는 해당 파일 내의 컴포넌트가 클라이언트 측에서만 실행된다는 것을 나타낸다.
이는 해당 파일이 서버 컴포넌트와 클라이언트 컴포넌트의 경계를 가지고 있으며 RSC의 컴포넌트 간 통신에서 다룰 수 있는 데이터에 차이가 있다는 것을 의미한다.
즉, props로 전달할 수 있는 데이터에 차이가 있다는 것이다.
결론적으로 말하면, 네트워크(서버)를 통한 데이터의 교환은 직렬화된 데이터로 통신해야 한다는 것이다.
use client 디렉티브가 있는 경우, 해당 파일 내의 하위 컴포넌트의 속성은 직렬화 가능해야 하며, 이는 네트워크를 통해 데이터를 전송하는 요구사항이다.
직렬화 가능한 데이터는 JSON으로 표현할 수 있는 데이터(객체, 배열, 문자열, 숫자, 불리언, null, undefined)를 가리킨다.
예를 들어, 객체나 배열은 JSON으로 다룰 수 있으므로, 직렬화된 데이터라고 할 수 있.
반면에, 함수는 객체로 다룰 수 있지만, JSON 객체로 표현할 수는 없기때문에 함수는 직렬화할 수 없는 데이터이다.
※ 참고) RSC를 사용하면, 무조건적으로 번들 크기가 감소하는 것은 아니라고 한다.
클라이언트 컴포넌트로 변경 하려면 "use client" 키워드를 페이지에서 상단에 표시해 주면된다.