리액트는 어플리케이션을 구축하고 구조화하는 방법에 대해 무관심하다. Next.js는 어플리케이션을 구조화하는 프레임워크로, 개발 프로세스와 최종 어플리케이션을 더 빠르게 만들도록 최적화를 제공한다.
Next.js는 어플리케이션의 development와 production 단계에 대한 기능을 모두 제공한다.
각 환경이 서로 다른 고려사항과 목표를 갖고 있기 때문에 어플리케이션을 development 단계에서 production으로 이동하려면 수행해야 할 작업이 많다. (ex. compile, bundle, minify, code split)
개발자들은 JSX, TypeScript 등 개발자 친화적인 언어를 이용하여 코드를 작성한다. 이러한 언어들은 개발자의 효율성을 향상 시키지만, 브라우저가 이해할 수 있는 자바스크립트로 컴파일 되어야 한다. Next.js에서 컴파일은 코드를 편집하는 개발 단계에 속하며 빌드 단계의 일부로 실행된다.
컴파일은 한 언어로 된 코드를 다른 언어나 해당 언어의 다른 버전으로 출력하는 프로세스를 말한다.
개발자는 사람이 쉽게 읽을 수 있도록 최적화된 코드를 작성한다. 코드는 주석, 공백, 들여쓰기, 여러 줄 등 코드를 실행시키는 데 불필요한 정보들을 포함한다. 축소는 코드의 기능을 유지하면서 주석과 불필요한 코드 형식을 제거하는 과정이다. 즉, 파일 크기를 줄여 어플리케이션의 성능을 향상시킨다.
개발자는 더 큰 규모의 어플리케이션을 구축하는 데 사용할 수 있는 모듈, 컴포넌트, 함수로 어플리케이션을 쪼갠다. 이러한 내부/외부 모듈과 외부 패키지를 가져오고 내보내어 파일 종속성을 가진 복잡한 웹을 생성한다. 번들링은 사용자가 웹 페이지를 방문할 때 요청하는 횟수를 줄이기 위해 파일이나 모듈을 최적화된 번들로 병합하는 것이다.
일반적으로 개발자는 어플리케이션을 다양한 URL에서 접근할 수 있는 여러 페이지들로 나눈다. 각 페이지들은 어플리케이션의 진입점이 된다. 코드 분할은 어플리케이션 번들을 각 진입점에 필요한 더 작은 덩어리로 분할하는 과정이다. 해당 페이지를 실행시키는 데 필요한 코드만 로딩하여 어플리케이션의 초기 로딩 시간을 개선한다. Next.js는 코드 분할 기능이 내장 되어 있다. pages/
디렉토리 내의 각 파일은 빌드 단계에서 자동으로 자바스크립트 번들로 코드 분할된다.
빌드 시간은 production을 위한 어플리케이션 코드를 준비하는 일련의 단계를 의미한다. 어플리케이션을 빌드하면 Next.js는 어플리케이션 코드를 production에 최적화된 코드로 변환한다.
런타임은 어플리케이션이 빌드되고 배포된 후에, 사용자의 요청에 대한 응답으로 어플리케이션이 실행되는 기간을 의미한다.
리액트에서 작성한 코드를 UI의 HTML 표현으로 변환하려면 피할 수 없는 작업이 있다. 이 프로세스를 렌더링이라고 한다. 렌더링은 서버나 클라이언트에서 발생하고, 빌드 시 미리 혹은 런타임 시 모든 요청에서 발생한다. Next.js를 사용하면 CSR, SSR, SSG의 세가지 유형의 렌더링 방법을 사용할 수 있다.
Next.js의 장점은 페이지 단위로 가장 적합한 렌더링 방법을 선택할 수 있다는 것이다.
Next.js는 기본적으로 모든 페이지를 사전에 렌더링 한다. 사전 렌더링은 HTML이 사전에 서버에서 생성된다. 생성된 HTML은 페이지에 필요한 최소한의 자바스크립트 코드만 가지고 있다. 페이지가 브라우저에 의해 로드되면 자바스크립트 코드가 실행되고 페이지가 상호작용할 수 있게 된다. 이 과정을 hydration이라고 한다.
만약 사용하고 있는 어플리케이션이 순수 리액트 어플리케이션이라면, 사전 렌더링이 일어나지 않는다.
CSR (not Pre-rendering)
표준 리액트 어플리케이션에서 브라우저는 서버로부터 빈 HTML을 받는다. 초기 렌더링 작업이 사용자의 장치에서 수행되기 때문에 렌더링되기 전까지 빈 화면을 보게 된다.
Next.js는 두 가지 사전 렌더링 형태를 가지고 있다. 페이지를 위한 HTML이 생성될 때 일어나는 차이점을 살펴보자.
SSR은 매 요청마다 HTML을 생성하는 사전 렌더링 방식이다. CSR의 경우, JSON 데이터와 자바스크립트를 사용하여 상호작용하는 컴포넌트를 만든다. 반면, SSR의 HTML은 렌더링 속도가 빠르지만 상호작용하지 않는 HTML 페이지를 표시한다.
SSG는 빌드 시에 HTML을 생성하는 사전 렌더링 방식이다. 콘텐츠는 빌드 시에 한 번 생성되고 CDN에 저장되어 매 요청마다 재사용된다.
npm run dev
처럼 개발 모드에서는 모든 요청에 대해 페이지가 사전 렌더링 된다. 이는 개발을 좀 더 쉽게 하기 위함이다.
💥 언제 SSR을, 언제 SSG를 사용하면 좋을까?
SSG는 빌드 시에 한 번 생성하여 재사용하기 때문에 서버가 모든 요청에 대해 페이지를 렌더링하는 것보다 훨씬 빠르다. 따라서 가능하면 SSG를 사용하는 것이 좋다.
하지만 사용자 요청에 앞서 페이지를 미리 렌더링할 수 없다면 SSG는 좋은 방법이 아니다. 예를 들어 자주 갱신되는 데이터를 표시하거나 매 요청 페이지 내용이 변경되는 경우가 있다. 이 경우에는 SSR을 사용하는 것이 좋다.
Next.js의 경우, 어플리케이션 코드를 원본 서버, CDN, Edge에 배포할 수 있다.
전통적으로 클라이언트나 서버에서 수행되었던 작업을 Edge로 이동시킴으로써 클라이언트로 전송되는 코드의 양이 줄고 사용자의 요청의 일부를 Edge가 처리하기 때문에 어플리케이션 성능의 향상으로 이어진다. Next.js는 미들웨어를 사용하여 Edge에서 코드를 실행시킬 수 있다.
정적 생성은 데이터 유무에 관계없이 수행될 수 있다. 여기서는 외부 데이터를 가져와야 하는 경우만 살펴보겠다.
일부 페이지의 경우, 외부의 데이터를 먼저 가져오지 않으면 HTML을 렌더링하지 못한다. 예를 들면 파일 시스템에 접근하거나, 외부 api를 가져오거나, 데이터베이스에 질의해야 하는 경우가 있다.
Next.js에서는 getStaticProps
를 이용하여 데이터와 함께 정적 생성이 가능하다. 페이지 컴포넌트를 내보낼 때 getStaticProps
라는 비동기 함수도 함께 내보낼 수 있다.
export default function Home(props) { ... }
export async function getStaticProps() {
const data = ...
return {
props: data
}
}
getStaticProps
는 페이지에서만 내보낼 수 있다. 페이지가 아닌 파일에서 내보낼 수 없다.
빌드 시점이 아니라 요청 시 데이터를 가져와야 하는 경우에는 SSR을 사용한다. SSR을 사용하기 위해서는 getServerSideProps
를 내보내야 한다.
export async function getServerSideProps(param) {
return {
props: ...
}
}
참고자료
NEXT.js : About Next.js
NEXT.js : Pre-rendering and Data Fetching