Next.js

Next.js 는 React.js 의 프레임워크이며 다음과 같은 기능들을 가지고 있다.

  • 더 빠른 page load 를 위한 자동 code splitting 과 prefetching 지원

  • Built-in CSS 과 Sass 지원, 그리고 모든 CSS-in-JS 라이브러리를 지원

  • pre-rendering (static generation(SSG) 와 server-side rendering (SSR) 모두에서) 를 지원한다.

  • 직관적인 page-based routing 시스템 (dynamic routes를 지원한다.)

  • fast page load (component state 유지한채로 page re-load 됨) 를 지원

  • Serverless Function 의 API endpoint 를 build 하기 위한 API routes 지원

  • 확장 가능성

위에 나열된 기능들을 하나씩 좀 더 자세히 살펴 보자

📕 code splitting 과 prefetching

Next.js 는 code splitting 을 자동으로 수행한다. 즉, 각 page 는 해당 page 에서 필요한 부분만 render 한다. 예를 들어, home page 가 render 되어 있을 때, 다른 page 의 code 들은 serve 되지 않는다.

이를 통해 우리의 app 에 수백개의 page 가 있더라도 home page 는 빠르게 load 된다.

이는 각각의 page 가 독립되어 있음을 뜻한다. 만약 한 page 에서 error 가 발생해도 app 의 다른 부분들은 여전히 동작한다.

추가적으로, Next.js 의 production build 에서는 브라우저의 화면 상 Link 컴포넌트가 발견되면 자동으로 연결된 page 의 코드들을 background 에 prefetch 한다. Link 를 클릭한 순간에는 이미 background 에 연결된 page 가 load 되어 있다. 이는 page 전환 속도를 매우 증가시킨다.

주의 사항

  • Next.js app 에서 외부 page 를 연결할 때는 <Link> 가 아닌 <a> 를 사용할 것
  • className 과 같은 attribute 는 <Link> 대신 <a> 에 추가할 것

Next.js 는 code splitting, client-side navigation 그리고 prefetching (in production) 을 통해 자동으로 app 을 최적화 시킨다.

📕 CSS Styling

Styling 방법 두 가지

SASS 를 사용하려면 ‘sass’ 라이브러리를 받아야한다. (.scss 로 CSS Modules 을 사용할 때도 마찬가지)

Global css 사용 방법

pages/_app.js 를 생성하고 css 파일을 해당 컴포넌트에서 import 한다.
(_app.js 컴포넌트는 모든 컴포넌트의 최상위 컴포넌트로 자동으로 인식되고 render 된다.)

pages/_app.js 코드

import "../styles/global.css";

export default function App({ Component, pageProps }) {
  return <Component {...pageProps} />;
}

pages/_app.js 를 추가하면 dev server 를 반드시 restart 해야한다.

📕 Pre-rendering

Next.js 는 기본적으로 모든 page 를 pre-render 한다. 즉, client-side JavaScript 대신 Next.js 가 각 page 의 HTML 을 미리 생성한다는 의미이다.
Pre-rendering 은 더 좋은 performance 와 SEO 를 이끌어낼 수 있다.

생성된 각각의 HTML 은 해당 page 에 필수적인 최소한의 JavaScript 코드와 관련이 있다.
page 가 browser 에 의해 load 되면, 해당 page 의 JavaScript 코드가 실행되면서 page 를 완전히 interactive 하게 만든다. (이 일련의 과정이 Hydration 이다.)

✅ Pre-rendering 확인 방법

아래의 순서를 따른다.

  1. browser 에서 JavaScript 를 disable 시킨다. (크롬에서 하는 방법)

  2. 링크로 이동해본다.
    해당 page 는 CRA 로 build 된 plain React.js app 이다. 즉, pre-render 기능이 없어 JavaScript 가 disabled 된 채로는 page 를 render 할 수 없다.

  3. 이번에는 다음 링크로 이동한다.
    해당 page 는 Next.js 로 이루어져 있으므로 JavaScript 가 disabled 된 채로도 pre-render 된 HTML 을 문제없이 보여준다.

Pre-rendering 의 동작 원리를 그림으로 확인해보자.

  • Pre-rendering
    최초 Load 시 HTML 을 Pre-render 해서 보여준다.
    JS 가 실행되면서 React 컴포넌트가 초기화 되고 page 를 interactive 하게 만든다. (Hydration)

    pre-rendering

  • No Pre-rendering (순수 React app)
    최초 Load 시 render 할 HTML 이 없다.
    JS 가 실행되면서 React 컴포넌트가 초기화 되고 page 를 interactive 하게 만든다. (Hydration)

    no pre-rendering

📕 Data Fetching

Build-time data fetching

static generation (정적 HTML 생성) 시 외부 Data 를 불러와 render 할 수 있다.
예를 들어 file system 에 접근하거나 외부 API 또는 database 에 query 를 보내는 등의 작업이 필요한 경우이다.

fetching data

이런 경우 Next.js 에 내장된 getStaticProps 라는 비동기 함수를 사용한다.

  • getStaticProps 는 production mode 에서 build 시에 실행된다.
  • 함수 내부에서 외부 data 를 fetch 해오고 page 의 props 로 보낼 수 있다.

구조는 다음과 같다.

// Home 컴포넌트
export default function Home(props) { ... }

// getStaticProps() 함수
export async function getStaticProps() {
  // file system, API, DB 등으로 부터 외부 data 를 불러온다.
  const data = ...

  // return 되는 객체의 'props' key 의 value 는
  // 자동으로 'Home' 컴포넌트의 props 로 전달된다.
  return {
    props: ...
  }
}

즉, getStaticProps 는 Next.js 에게 해당 page 에 data 의존성이 존재하는 것을 알리고 build 중 pre-render 시에 그 것을 먼저 해결하도록 한다.

development mode 에서는 편의 상 매 요청마다 getStaticProps 가 실행된다.

getStaticProps 를 통해 외부 API 와 Database 에 접근할 수 있는 이유는 이 함수가 오직 server-side 에서만 실행되기 때문이다. 절대 client-side 에서는 실행되지 않는다. 즉, browser 의 JS bundler 에도 포함되지 않는다. 그러므로 database 에 직접 보내는 query 와 같은 코드를 browser 에 보내지 않고도 사용할 수 있다.

오직 Page 에서만 사용 가능하다.

getStaticPropspages 폴더 내의 page 파일에서만 export 가능하다.

그 이유 중 하나는 React 가 page 를 render 하기 전에 모든 data 를 필요로 하기 때문이다.

Development 모드 VS. Production 모드

  • development 모드에서는 getStaticProps 가 매 요청(request)마다 실행된다.

  • production 모드에서는 build 시에 실행된다.

build 시에 실행되는 것이 원래의 목적이므로 request 시에만 사용 가능한 data 들에는 접근할 수 없다. 예를 들어 query parameter(url) 나 HTTP header 의 data 에 접근할 수 없다.

Request-time data fetching

만약 request 시에 data fetching 과 HTML 생성이 필요하다면 Server-side Rendering 을 하는 것이 좋다.

server-side Rendering 을 하기 위해서는 getServerSideProps 를 사용한다.

function Page({ data }) {
  // Render data...
}

// This gets called on every request
export async function getServerSideProps() {
  // Fetch data from external API
  const res = await fetch(`https://.../data`);
  const data = await res.json();

  // Pass data to the page via props
  return { props: { data } };
}

export default Page;

getServerSideProps 의 특징

  • getServerSideProps 가 request 시에 호출되므로, 매개변수인 context 는 request 의 세부 parameter 를 담고 있다.

  • getServerSideProps 는 request 시에 외부 data fetching 이 필요한 page 를 pre-render 해야할 때만 사용해야 한다.

  • server 가 매 request result 를 연산해야하기 때문에, TTFB(Time To First Byte) 는 getStaticProps 보다 느려진다.

  • result 는 추가적인 configuration 없이는 CDN 에 cache 될 수 없다.

Client-side Rendering

만약 data 를 pre-render 할 필요가 없고 data 가 자주 update 되어야 한다면, Client-side Rendering 을 사용할 수 있다.

  • 외부 data 가 필요하지 않은 page 의 일부를 정적 생성을 사용해 pre-render 한다. (loading ui 를 사용해 data 가 render 될 부분을 표시할 수 있다.)
  • page 가 load 되면, client 에서 JavaScript 를 사용해 외부 data 를 fetch 하고 남은 부분을 생성해 page 에 붙인다.

Vanilla JS 를 사용한 기초적인 Web app 의 경우인 것 같다.

client-side rendering 그림으로 이해하기

client-side rendering

client-side rendering 사용 예

user dashboard page 를 예로 들 수 있다. user 의 개인정보가 포함된 page 이므로 SEO 와 관련이 없고 pre-render 또한 필요 없기 때문이다.
(data 는 빈번하게 update 되므로 request-time data fetching 이 필요하다.)

Data Fetching 한 줄 요약

Build-time data fetching

  • getStaticProps : production 모드일 때 build 시 server-side 에서 실행되는 함수

Request-time data fetching

  • Server-side Rendering : getServerSideProps 함수 / request 시 외부 data fetching 이 필요한 page 를 pre-render 할 때 사용

  • Client-side Rendering : SEO 와 관련 없고 data 가 자주 update 돼야 할 경우 사용

Reference

Next.js 공식 문서

Server-side rendering 과 Client-side rendering 의 개념 설명