NextJS는 어떻게 이루어져있는가?

nyongho·2023년 4월 18일
0

NextJS

목록 보기
3/6

환경에 따른 NextJS

개발 단계에서 NextJS는 개발자에게 애플리케이션을 구축하는데 최적화 된 경험을 제공한다. 여기에는 TypeScript, ESLint, Fast Refresh 등이 있다.

프로덕션 단계에서 NextJS는 실 사용자와 애플리케이션을 사용하는 최적화 된 경험을 제공한다. 여기에는 성능과 접근성을 높이기 위한 것에 초점이 맞춰져있다.

개발단계에서 프로덕션단계로 넘어가는데는 다양한 작업이 필요한데 예를 들어 컴파일, 번들, 축소 및 코드 분할등이 필요하다.

Next.js는 이러한 코드 변환 및 기본 인프라의 대부분을 처리하여 애플리케이션이 프로덕션으로 쉽게 전환되도록 한다. 이것은 Next.js가 저수준 프로그래밍 언어인 Rust로 작성된 컴파일러와 컴파일, 최소화, 번들링 등에 사용할 수 있는 플랫폼인 SWC를 가지고 있기 때문에 가능한 것이다.

컴파일 (Compiling)

개발자는 개발자 친화적인 JSX, TypeScript, 최신 JS와 같은 언어를 사용해 코드를 작성하고 이는 개발자로 하여금 자신감과 능률 향상을 불러오지만 브라우저는 이런 개발 친화적 언어를 이해하지 못하므로 다시 JavaScript로 컴파일 하는 과정이 필요하다.

여기서 컴파일이란 한 언어로 작성 된 코드를 다른 언어 또는 해당 언어의 다른 버전으로 출력하는 프로세스를 의미한다.

Next.JS 에서는 개발 단계에서 코드를 수정하는 단계에서 컴파일이 발생하며 프로덕션으로 빌드하는 준비 단계 중 발생한다.

출처: Next.js 공식문서

코드 축소 (Minifying)

개발자는 사람이 읽기 좋게 코드를 짠다. 이 코드에는 주석, 공백, 들여쓰기, 여러 줄 등 코드 실행에 필요하지 않은 추가 정보가 포함될 수 있는데 이로 인해 파일의 크기가 커지는 불참사가 발생한다. 코드 축소 기능은 이러한 불필요한 정보들을 제거해준다.

Next.js에서는 프로덕션 단계에서 JS와 CSS의 코드 축소가 이루어진다.

출처: Next.js 공식문서

번들링 (Bundling)

개발자는 응용 프로그램을 더 큰 응용 프로그램을 구축하는 데 사용할 수 있는 모듈, 컴포넌트 및 기능으로 나눈다. 이러한 내부 모듈과 외부 패키지를 내보내고 가져오는 과정에서 파일 종속성의 복잡한 웹이 생성된다.

번들링은 사용자가 웹 페이지를 방문할 때 파일에 대한 요청 수를 줄이기 위해 브라우저에 최적화된 번들로 파일을 병합하는 과정을 말한다.

코드 분할 (Code Splitting)

개발자는 일반적으로 애플리케이션을 서로 다른 URL에서 액세스할 수 있는 여러 페이지로 나눈다. 코드 분할은 애플리케이션의 번들을 각 진입점에 필요한 더 작은 청크로 분할하는 프로세스이며 목표는 해당 페이지를 실행하는 데 필요한 코드만 로드하여 애플리케이션의 초기 로드 시간을 개선하는 것이다.

Next.js에서는 기본적으로 코드 분할이 내장되어있으며 pages폴더 내부에 있는 각 파일들은 빌드 단계에서 자동으로 코드 분할이 이루어진다.

빌드타임 & 런타임

빌드타임은 프로덕션 단계를 위해 애플리케이션 코드를 준비하는 과정을 의미한다. (빌드단계)

애플리케이션을 빌드할 때 Next.js 에서는 최적화를 위해 다음의 파일들을 변환한다.

  • HTML files for statically generated pages
  • JavaScript code for rendering pages on the server
  • JavaScript code for making pages interactive on the client
  • CSS files

런타임은 애플리케이션이 빌드 및 배포 된 이후 사용자 요청에 대한 응답으로 애플리케이션이 실행되는 시간을 의미한다. (요청 시간)

렌더링

나의 UI를 위해 React에서 작성하는 코드를 HTML로 변환하는 작업에서 발생되는 과정을 렌더링이라고 한다. 렌더링은 서버 혹은 클라이언트에서 이루어질 수 있다. 즉 빌드타임 혹은 런타임에서 이루어질 수 있다.

기존 React에서는 CSR만 제공했다면 NextJS에서는 Server-Side Rendering, Static Site Generation, Client-Side Rendering 세 렌더링 방법을 제공한다.

Pre-Rendering

리액트 컴포넌트를 HTML로 변환하고 외부의 데이터를 패치하는 과정이 클라이언트에게 보여지기 전에 이루어지기 때문에 SSR과 SSG를 묶어 보통 Pre-Rendering이라고 일컫는다. (미리-렌더링해둔다 정도로 이해하면 될 듯하다.)

Client-Side Rendering (CSR) vs Pre-Rendering

이것저것 설치않은 일반 리액트는 기본적으로 자바스크립트가 UI를 구축하기전까지 브라우저는 서버에서 보낸 빈 HTML화면만 받게된다.
따라서 초기 렌더링이 클라이언트쪽(유저 브라우저)에서 이루어지기 때문에 CSR이라고 불린다.

Next.js에서는 특정 컴포넌트에서 CSR을 이용할 수 있다! useEffect를 이용한 데이터 패칭이나 useSWR같은 데이터 패칭 훅을 이용하면 된다.

반대로 Next.js에서는 모든 페이지에 pre-rendering이 기본적으로 적용된다. 즉 CSR에서는 브라우저가 자바스크립트를 통해 렌더링이 이루어졌다면 Pre-rendering은 서버에서 미리 생성됨을 의미한다.

이를 통해 CSR이 가지고 있는 자바스크립트가 다운되기 전까지는 빈 화면을 보여줘야 하는 단점을 극복할 수 있다. 이는 곧 사용자 경험의 상승으로 이어진다.

이제 Pre-rendering의 두 종류에 대해 알아보자.

Server-Side Rendering (SSR)

SSR, 서버 사이드 렌더링에서는 각 요청마다 서버측에서 HTML 페이지들이 생성된다. 그런 다음 HTML, JSON 데이터, 자바스크립트 지침이 클라이언트로 전송된다.

클라이언트에서 HTML은 상호작용 없는 빠른 속도의 정적 페이지를 보여주는 반면, 리액트는 JSON과 JS를 사용하여 만들어진 컴포넌트들을 상호작용 가능하게 만든다. (예를 들어 버튼에 이벤트 핸들러를 첨부하는 것). 이 과정을 Hydration (물을 주는 행위)이라고 한다.

Next.js에서는 getServerSideProps를 사용하여 서버 사이드 렌더링 페이지를 선택할 수 있다.

React 18 및 Next 12에는 React 서버 컴포넌트의 알파 버전이 도입되었습니다. 서버 컴포넌트는 서버에서 완전히 렌더링되며 렌더링을 위해 클라이언트 측 JavaScript가 필요하지 않습니다. 또한 서버 컴포넌트를 사용하면 개발자가 일부 로직을 서버에 유지하고 해당 로직의 결과만 클라이언트에 보낼 수 있습니다. 이렇게 하면 클라이언트로 전송되는 번들 크기가 줄어들고 클라이언트 측 렌더링 성능이 향상됩니다.

Hydration (수화 = 물을 주는 행위)

Hydration이 등장하게 된건 당연 CSR의 한계 때문이었다. 리액트를 이용한 애플리케이션을 빌드한 이후 클라이언트 요구사항이 높아짐에 따라 자바스크립트 용량 또한 높아지게 되고 SEO의 한계에 부딪힌 개발자들은 서버 측에서 먼저 정적페이지를 렌더링하고 번들된 JS파일을 둘 다 클라이언트에 보내는 생각을 했지만 이렇게 되면 해당 DOM에는 기존 리액트의 인터렉티브한 애플리케이션이 하나도 없는 상태가 되버렸다. 근데 이것이 이후 React16 부터 가능하게 됐다.

즉 버튼을 클릭했을 때 발생해야할 이벤트 핸들러 함수들을 기존의 클라이언트에서 만들어진 리액트 DOM이 아닌 서버에서 만들어진 정적인 DOM에 붙혀 동적으로 상호작용이 가능하도록 바꾸어주는 기능이 Hydration이다. (메말랐던 HTML에 물을 주는 행위)

Pre-rendering: 컴포넌트의 초기상태를 기반으로 미리 렌더링된 html을 클라이언트로 넘김 => 페이지의 초기 로딩 지연을 줄일 수 있음

Hydration : pre-render된 페이지에 react의 상호작용을 입히는 것

이를 통해 더 빠른 "First paint" (초기 로딩 속도)를 실현시킬 수 있게됐지만 SSR 특유의 고질병이 발생하게 되는데

유저: 화면에 내용은 보이는데 클릭해도 아무런 반응이 없어요

라는 문제가 나타나게 된다.

기존 리액트는 HTML은 비어있고 이 빈 HTML의 <div id="root"></div> 밑에 JS와 DOM 메소드를 이용해 컨텐츠를 추가하는 개념이므로 JS를 다운로드 받기전까지 UI를 보여줄 수 없었지만 NextJS에서는 Pre-rendering을 통해 HTML에 우리가 작성한 UI를 일단 보여줄 수 있다. 하지만 결국 어떤 이벤트를 행하기 위해서는 JS가 필요하고 결국 다운로드를 해야한다는 사실은 변하지 않는다. 따라서 HTML은 보이는데 JS는 아직 다운로드 되지 않아 클릭 이벤트가 발생하지 않는 참사가 발생하는 것이다.

이렇게 되면 좋은 유저 경험을 위해 초기로딩속도를 빠르게 한 것과 모순되게 웹페이지가 동작하지 않는 것처럼 느끼는 (실제로는 JS가 다운로드 중) 더 나쁜 유저 경험을 선사하게 된다.

이를 해결하기 위해 React18에서는 선택적 Hydration 기술을 업데이트한다.

즉, 렌더링이 오래걸리는 컴포넌트를 Suspense를 이용해 독립적으로 Hydration 시킬 수 있었지만 만약 유저가 그 컴포넌트가 아닌 다른 컴포넌트를 먼저 클릭한다면 이미 Hydration은 오래걸리는 곳 먼저 이루어지고 있으므로 아무런 반응이 없게 되는데 이를 해결하기 위해 React18에서는 유저가 클릭한 곳이 먼저 Hydration되게 바뀌게 했다.

Static Site Generation (SSG)

SSG (정적 사이트 생성)은 HTML이 서버측에서 생성되지만 런타임 환경에서 서버에게 응답을 받지 않으므로 서버 사이드 렌더링과는 다르다. 대신, 해당 페이지는 애플리케이션이 배포될 때 빌드 타임에서 딱 한 번 생성되며 CDN에 해당 HTML이 저장되고 각 요청마다 재사용되게 된다.

Next.js에서는 getStaticProps를 이용해 선택적으로 정적 페이지를 생성할 수 있다.

만약 SSG로 만들어진 정적 페이지에 뭔가 추가하거나 업데이트하고 싶다면 Incremental Static Regeneration을 사용하면 된다. 한마디로 정적페이지로 만들어진 사이트에 데이터를 추가하기 위해 사이트 전체를 다시 리빌드하지 않아도 된다는 뜻이다.

NextJS의 매력은 CSR, SSR, SSG을 페이지마다 필요한 곳에 선택적으로 사용할 수 있다는 것이다.

Network

애플리케이션 코드가 저장되고 네트워크에 배포된 이후 코드가 어떻게 실행되는지 그 위치를 아는 것이 중요하다. 네트워크를 리소스를 공유할 수 있는 컴퓨터로 생각할 수도 있다. Next.js에서는 나의 애플리케이션 코드를 Origin Server와 Content Delivery Networks(CDN)과 Edge에 배포한다.
그럼 각각이 무엇인지 한 번 알아보자!

Origin Servers

앞에서 설명했듯이 서버는 내 원본 애플리케이션 코드를 저장하고 실행하는 메인 컴퓨터를 참조한다. 우리는 CDN 서버와 Edge 서버를 다른 위치에 애플리케이션 코드가 배포되는 서버와 구별하기 위해 Origin Server라는 용어를 사용한다.

Origin Server가 요청을 받으면 응답을 보내기전에 약간의 계산을 거친다. 그리고 이 계산 결과는 CDN으로 이동한다.

Content Delivery Network

CDN은 정적 컨텐츠 (HTML과 이미지 파일들)를 전 세계 다양한 지역에 저장한 뒤 클라이언트와 Origin Server 중간에 위치한다. 그리고 새로운 요청이 들어오면 현재 유저에게서 가까운 CDN 서버에서 캐쉬된 결과를 응답으로 보낸다.

각각의 요청마다 응답을 하는일이 벌어지지 않으므로 원본 서버의 로딩 속도를 줄일 수 있다. 또한 유저에게는 지리적으로 더 가까운 위치에서 응답이 오기 때문에 사용자는 더 빠른 웹을 경험할 수 있다.

Next.js에서는 Pre-rendering을 미리 수행할 수 있으므로 CDN은 정적 결과를 저장하는데 아주 적합하다. 이를 통해 컨텐츠를 아주 빠르게 전송할 수 있다.

The Edge

CDN과 유사하게 에지 서버는 전 세계 여러 위치에 배포된다. 그러나 정적 콘텐츠를 저장하는 CDN과 달리 일부 에지 서버는 작은 코드 조각들을 실행할 수 있습니다. 이는 캐싱과 코드 실행 모두 사용자에게 더 가까운 에지에서 수행할 수 있음을 의미한다. 기존에 클라이언트 측 또는 서버 측에서 수행되었던 일부 작업을 Edge로 이동하면 클라이언트로 전송되는 코드의 양이 줄어들고 사용자 요청의 일부가 모두 처리될 필요가 없기 때문에 애플리케이션의 성능을 높일 수 있다. 원본 서버로 돌아가는 방법 - 따라서 대기 시간이 줄어듭니다. 여기에서 Next.js를 사용한 Edge 예제를 참조하세요.

Next.js에서는 Edge를 미들웨어를 통해 코드를 실행할 수 있고 곧 React 서버 컴포넌트 또한 가능하게 될 것이다.

profile
두 줄 소개

0개의 댓글