요즘 가장 각광받는 프론트앤드 기술은 NextJS라고 할 수 있다. 하지만 많은 사람들이 왜 NextJS를 사용하냐고 물어보면 SSR과 SEO에 대해서 이야기하지만 더 구체적인 답변을 못하는 경우가 많다. 필자도 그랬었기 떄문에 이번 글에서는 NextJS의 특징과 NextJS가 탄생하게된 배경을 알아보고자 한다.
해당 글은 NextJS 14 버전을 다룹니다.
NextJS는 리액트 서버 컴포넌트(이하 RSC)를 기반으로 하는 SSR 프레임워크로 잘 알려져 있지만 이는 반은 맞고 반은 틀리다. 왜냐하면 SSR만을 지원하는 프레임워크가 아니기 때문이다. 오히려 NextJS는 SSR보다는 Static Site Rendering(이하 SSG)을 권장하며 기본 설정값이기도 하다. 그럼 우선 NextJS의 렌더링 방식부터 알아보자.
많은 사람들이 NextJS의 가장 큰 특징을 SSR이라고 생각하지만 SSG를 간과해서는 안된다. SSR과 SSG의 차이점은 HTML이 생성이 어느 시점에 이뤄지는 지에 따라 다르며 고유한 특징이 있기 때문에 각 상황에 적합한 렌더링 방식이 뭔지 정확하게 알아야 한다.
Static Site Rendering: 빌드 타임때 HTML이 생성되며 각 요청때마다 재사용한다.
Sever Side Rendering: 클라이언트가 요청하면 서버에서 생성한 HTML을 전달한다.
SSR과 SSG는 엄연히 다른 렌더링 방식이다.
Pages 라우터(12 버전 이하)까지만 하더라도 NextJS는 개발자에게 렌더링 방식을 선택할 수 있게했지만 App 라우터(13 버전 이상)부터는 NextJS가 자체적으로 렌더링 방식을 최적화한다.
SSG의 가장 큰 특징은 빌드 타임때 HTML이 생성된다는 것이다. 이 결과는 CDN에 캐싱되어 특정한 상황 이외에는 계속 재사용된다. 서버 혹은 클라이언트에서 렌더링이 필요 없기 때문에 렌더링 속도가 매우 빠르며 많은 리소스를 소모하지 않는다.
하지만 SSG가 모든 상황에서 적합한 것은 아니다. 왜냐하면 이름 그대로 정적인 사이트를 렌더링할때만 의미가 있기 때문이다. 여기서 정적이란 의미는 유저와 관계없이 항상 같은 결과물을 렌더링하는 것을 의미한다. 그래서 유저와 관계없이 보여지는 페이지를 보이는 블로그 포스트나 제품 페이지를 보일때 매우 유용한다.
NextJS의 SSG를 사용햔 브라우저 성능 개선에 대해 깊게 알고 싶다면 카카오 기술 블로그의 글 을 추천한다.
NextJS는 SSR를 Dynamic Rendering이라고 지칭하는데 이는 매 요청마다 HTML을 만들기 때문에 그런 것 같다. 이런 특징때문에 SSR은 쿠키나 URL의 searchParams를 사용해서 개인화된 페이지를 렌더링할때 유용하다.
앞서 App 라우터에서는 NextJS가 자체적으로 렌더링 방식을 택한다고 했는데 그렇다고 해서 유저가 렌더링 방식을 선택할 수 없는 것은 아니다. 왜냐하면 NextJS는 기본으로 SSG를 사용하되 몇 가지 상황에 한해서만 다른 방식으로 렌더링하기 때문이다. 대표적으로 Dynamic Functions을 사용하면 NextJS는 SSG대신 SSR을 사용한다. 혹은 Client Component도 서버에서 렌더링된 후에 클라이언트에서 hydrate된다.
Hydrate란 DOM에 이벤트를 부여하는 작업을 의미하며 순수 HTML에 자바스크립트를 적용시킨다고 이해하면 편하다. 마른 땅(정적)에 물(동적)을 부여한다는 의미로 Hydrate란 단어가 쓰인 것으로 추정하고 있다.
NextJS의 렌더링 방식을 살펴봤으니 이제는 NextJS가 지원하는 두 가지 컴포넌트 형태를 알아보자. NextJS에는 서버 컴포넌트와 클라이언트 컴포넌트가 있는데 리액트를 사용해봤다면 클라이언트 컴포넌트는 익숙하지만 서버 컴포넌트는 조금 낯설 수 있다. 하지만 NextJS에서는 클라이언트 컴포넌트도 CSR과 조금 다르게 동작하니 유심히 살펴보자.
서버 컴포넌트를 처음 배웠을 때 클라이언트 컴포넌트랑 뭐가 다른 거지 라는 생각을 많이 했었는데 서버에 가까이 위치한 컴포넌트라고 생각하니 그 특징들을 이해하기 수월한 것 같다. 서버 컴포넌트가 가진 대표적 특징은 다음과 같다.
위 특징들을 그냥 외운다고 생각하면 쉽지않지만 서버와 가까이 위치해 있어서 가진 장점들이라고 생각하면 쉽게 와닿을 수 있을 것이다.
서버 컴포넌트의 렌더링은 서버와 클라이언트 모두에서 이뤄지는데 분리하면 다음과 같다.
RSC payload는 이진 데이터화된 서버 컴포넌트로 렌더링과 reconciliation에 사용된다.
리액트에서 컴포넌트는 CSR로 렌더링하지만 NextJS는 상황에 따라 SSR과 CSR 모두 사용한다. SSR로 클라인트 컴포넌트를 렌더링하는 상황은 첫 페이지를 로드할때 이다. 사용자에게 최대한 빨리 페이지를 보여주기 위해서 서버에서 렌더링한 후에 클라이언트에서 hydrate한다. 반면에 CSR로 렌더링되는 경우는 새로고침된 페이지나 다른 라우터로 이동했을 때이다.
NextJS는 어떻게든 CSR의 단점을 극복하려고 하는 의지가 참 돋보이는 것 같다.
RSC가 아직 안정화되지 않은 시점에서 React는 CSR만으로 어플레키이션을 구축하지만 NextJS는 SSG과 SSR 그리고 CSR를 복합적으로 사용함으로서 각 방식이 가진 단점을 다른 방식의 장점으로 상쇄한다. 그 중에 핵심적인 역할을 하는 기술은 RSC라고 생각하며 서버에서 생성되는 이점을 십분 활용하고 있다.
NextJS가 가지고 있는 장점들을 단지 SSR과 SEO로 점철하기에는 너무나도 방대하고 깊은 브라우저 최적화 기능과 원리가 담겨 있다. 이 글에서는 아주 얕게만 다뤘으니 더 깊게 알아보고 싶다면 NextJS 공식 문서를 적극 추천한다. 리액트 공식 문서만큼 잘 정리되어 있다.
이제 NextJS가 가진 특징들을 알아보았으니 NextJS가 어떤 페인 포인트를 해소함으로 떠올랐는지 알아보자.
NextJS는 어떻게 개발자들의 마음을 사로잡았을까? 어떤 이들은 브라우저 최적화라고 말할 수도 다른 이들은 뛰어난 DX라고 말할 수도 있다. 그러나 NextJS가 만들어진 이유를 물어보면 단연코 CSR의 문제를 해결하기위해 태어났다고 말할 것이다. 그렇다면 CSR이 어떤 문제를 갖고 있길래 모두가 같은 대답을 할까?
페이지 간 HTML을 이동하는 옛 방식을 사용해보면 CSR을 이용한 SPA가 얼마나 뛰어난 UX를 제공하는지 단번에 경험할 수 있다. CSR이란 사용자 환경에서 어플리케이션을 구동하는 것으로 첫 로드시 모든 데이터를 다운받아야 하지만 한번 다운받으면 페이지 로딩은 매우 빠르다. 서버로부터 렌더링에 필요한 데이터를 다시 받을 필요가 없기에 API 관련 요청을 제외하고는 페이지를 로딩하는데 어떤 비동기적 요청의 제한이 없다.
이외에도 빠른 웹 반응성, 적은 서버 로드 등 여러 장점으로 React,Vue, Angular 삼대장이 프론트앤드를 점령한 이후 CSR은 꾸준하게 사용되었다.
CSR의 흐름 - 출처: https://prismic.io/blog/client-side-vs-server-side-rendering
그러나, CSR은 자바스크립트 파일을 모두 다운로드하고 파싱할때까지 어떤 화면도 렌더링되지 않는다는 치명적인 단점을 갖고 있는데 이는 곧 사용자 리텐션을 낮추는 행위이기 때문이다. 더군더나 UX를 향상시키기 위해 JS 파일 크기는 계속 커져만 갔다.
먼저 명확히 해야할 것이 SSR이 CSR의 상위호환이 아니다. 그리고 그 반대도 성립되지 않는다. 저 둘은 단지 방식이 달라 다른 특징을 가지고 있다.
다시 돌아와서 위에서 언급했듯이 유저 이탈을 막기 위해서 초기 화면은 절대 늦게 렌더링되면 안된다. 그러나, CSR은 그런 면에서 뒤쳐지는 특징을 가지고 있었고 SSR은 초기 렌더링에 뛰어났다. 왜냐하면 클라이언트에서 코드를 다운받을 필요가 없기 때문이다.
즉, 서버에서 완성된 화면을 받아서 렌더링하면 된다는 말이다.
SSR이 초기 렌더링은 진짜 빠르다.
그리고 이를 극대화하는 것이 NextJS의 SSG인데 서버에서 만들지도 않고 빌드 타임때 만들어진 결과물을 전달하기 때문이다. 심지어 이를 캐싱해놓고 같은 요청이 들어오면 캐싱된 값을 보낸다.
NextJS는 이외에도 API 요청의 응답도 캐싱해놓는데 이 문서를 읽어보는 것을 추천한다.
앞서 언급했듯이 NextJS는 SSR뿐만 아니라 CSR과 SSG까지 활용해서 브라우저 성능을 최적화한다. 사용자와 관계없이 모두 같은 페이지가 보여진다면 SSG를, 사용자에 맞게 페이지가 보여져야한다면 SSR을, 상태에 맞게 렌더링되야한다면 CSR를 사용한다.
듣고보니 NextJS가 팔방미인인 것 같다. NextJS는 그럼 단점이 없을까?
NextJS의 단점은 너무나 많은 기능을 가지고 있다는 것이다. 클라이언트와 분리된 서버 컴포넌트에서 API와 데이터 작업을 모두 집행할 수 있기 때문에 어플리케이션의 복잡성이 손쉽게 증가할 수 있다. 그리고 아직 RSC가 안정화되지않은 만큼 NextJS의 안정성도 완전히 확보됐다고 할 수 없다. 매년 업그레드되는 버전마다 나오는 파격적인 기술때문에 학습은 더 어려워지고 복잡해지는 것은 덤이다.
NextJS 14의 Server Action은 php의 재림이라며 이미 많은 질타를 받고 있다.
그러니 무조건 NextJS를 사용하는 것보다 SSR과 SSG가 꼭 필요한 어플리케이션에 사용하는 편이 좋다.
NextJS는 SSR과 SEO만으로 설명하기에는 너무나 방대하고 깊은 개념들이 많다. 이 글만으로는 부족하지만 아주 조금이나마 NextJS를 이해하는데 도움이 되기를 바란다. 이건 사견이지만 NextJS의 진가는 캐싱이라고 생각한다. 원래는 클라이언트가 일일이 결과를 만들어냈다면 서버에서 만들어놓은 결과를 전달하는 방식이 참 효율적인 것 같다. 그것이 HTML이든 API 요청이든 말이다.