Frontend/React

Next.js - getStaticProps vs getServerSideProps vs getStaticPaths vs getInitialProps

둉이 2022. 6. 12. 02:40

Next.js는 서버 사이드 렌더링 방식(SSR)을 제공하는 React의 풀스택 프레임워크이다.

 

초기 접근 시에는 SSR 방식으로 페이지를 생성하고, 페이지 이동을 할 때는 CSR 방식으로 동작하기 때문에 부드러운 화면 전환이 가능하다.

 

SSR와 CSR 방식의 특징 및 차이점은 아래 블로그 글에 정리해 놓았다.

 

페이지 렌더링 방식 비교: SSR vs CSR vs SSG

SSR(Server-side Rendering) SSR은 서버에서 페이지를 렌더링하는 방식으로, Next.js와 PHP, ASP 등이 이에 속한다. SSR의 동작 방식은 다음과 같다. 1. 클라이언트가 특정 페이지를 요청한다. 2. 서버에서는 해.

guiyomi.tistory.com

 

 

Next.js는 일반적인 리액트 어플리케이션과는 달리 서버에서 페이지를 Pre-rendering하는 SSR 방식을 제공하므로 검색엔진 최적화에 적합하다.

 

크롬에서는 마우스 오른쪽 클릭 - 페이지 소스 보기에서 간단하게 SSR이 적용된 페이지 HTML을 확인할 수 있다.

페이지 소스 비교(SSR vs CSR)

 

일반적인 SPA 어플리케이션은 브라우저 단에서 페이지 렌더링이 일어나므로(CSR) 페이지 소스를 확인해 보면 빈 HTML 코드만 존재한다.

 

이 때문에 일부 검색 엔진에서는 CSR 방식으로 생성된 페이지 수집에 어려움을 겪는다.

 

하지만 SSR 방식으로 생성된 페이지의 경우에는 서버에서 미리 페이지를 생성하여 완성된 HTML 문서를 클라이언트(브라우저)에 전달하므로 페이지 소스 내에 HTML 마크업이 포함되어 있는 것을 확인할 수 있다.

 

 

또한 서버에서 데이터를 받아온 후 표출해야 하는 페이지를 구현해야 하는 경우에 적합하다.

 

일반적으로 백엔드 API에서 json 데이터를 가져와서 화면에 표출하는 리액트 어플리케이션은 최초 페이지 렌더링 시에는 API 응답을 받지 못한 상태이므로 state는 undefined 값을 갖는다.

 

이러한 상태에서는 별도의 처리를 하지 않는 경우에는 빈 화면이 표출되므로, 로딩 화면을 보여주거나 스켈레톤 UI를 표출하게끔 처리를 해야 한다.

 

API 응답을 받아온 이후에는 해당 데이터로 state 값이 변경되며, 페이지가 리렌더링되면서 정상적으로 화면이 표출된다.

 

이러한 CSR 기반 페이지 렌더링 방식은 불필요한 렌더링(2회 렌더링)이 발생하며, API 응답이 도착하기 전까지 사용자가 원하는 페이지를 볼 수 없다는 단점이 있다.

 

 

Next.js에서는 Pre-Rendering을 위한 함수로 getStaticProps, getServerSideProps, getStaticPaths를 제공한다.

 

getStaticProps는 SSG(Static Site Generation), getServerSideProps는 SSR(Server Side Rendering) 방식으로 동작한다.

 

각각의 함수에 대한 특징과 장/단점, 사용 방법에 대해 알아보자.

 

 


 

getStaticProps

getStaticProps는 Next.js 어플리케이션을 빌드할 때 최초 한 번만 실행되는 정적 사이트 생성(SSG, Static Site Generation) 함수이다.

 

다시 빌드를 하지 않는 이상은 실행되지 않으므로 props 값이 변경되지 않으며, 데이터가 거의 변경되지 않는 블로그, 이벤트 홍보 페이지 등의 정적 페이지를 렌더링하는 데에 적합하다.

 

getStaticProps는 네트워크 요청에 필요한 리소스를 아낄 수 있고 속도가 빠르며, 캐싱이 가능하다는 장점은 있지만 동적인 데이터 요청에는 부적절하다.

 

이를 보완할 방법으로 점진적 정적 재생성(ISR, Incremental Static Regeneration) 방식의 revalidate 속성이 있다.

 

revalidate 속성에 업데이트 주기(second) 값을 전달하면 지정한 초 단위로 페이지가 재생성되기 때문에 재빌드를 하지 않아도 주기적으로 props를 업데이트할 수 있다.

 

다음과 같이 revalidate: 10을 할당한다면 서버에서는 10초에 한 번씩 해당 페이지를 다시 생성한다.

const 페이지_컴포넌트 = (props) => {
  // 페이지 컴포넌트
};

export default 페이지_컴포넌트;

export const getStaticProps = async () => {
  // API 호출 혹은 파일 시스템 접근 등의 코드 작성
	
  return {
    props: {
      propsName: propsValue,
      // 여기서 페이지 컴포넌트에 넘겨줄 props를 리턴해야 함
    },
    revalidate: 10,  // 점진적 정적 생성(n초에 한 번은 페이지를 다시 생성)
  };
};

 

 

그 외에도 getStaticProps 함수는 redirect와 notFound 속성을 제공한다.

 

redirect는 사이트의 특정 path 혹은 외부 url로 리다이렉션할 수 있는 속성으로, destination과 statusCode, permanent 값을 인자로 전달할 수 있다.

export const getStaticProps = async () => {
  // API 호출 혹은 파일 시스템 접근 등의 코드 작성
	
  return {
    redirect: {
      destination: '/add',  // add 경로로 이동
      permanent: false,
    },
  };
};

 

destination은 리다이렉션 경로를 나타내며, string 값을 갖는다.

 

statusCode는 리다이렉트 경로로 함께 보내줄 상태 코드로 301, 302, 303, 307, 308 중 하나의 값을 갖는다.

 

permanent는 영구적 리다이렉션 여부로, boolean 값을 갖는다.

 

permanent: true 속성을 사용하게 되면 브라우저와 검색 엔진에 영구적으로 리다이렉션을 캐싱하게 되며, 상태 코드로는 308을 사용한다.

(참고로 statusCode와 permanent 속성은 동시에 사용할 수 없다.)

 

 

notFound는 boolean을 값으로 갖는다.

 

true인 경우에는 404 에러를 발생시키며, pages 폴더 하위에 404.js라는 이름으로 에러 페이지를 생성한 경우에는 해당 페이지가 표출된다.

 

 

또한, getStaticProps는 함수의 인자로 context를 받아올 수 있다.

 

context에는 다음과 같은 값이 포함되며, req, res와 같은 http 객체에 접근할 수는 없다.

- params(동적 파라미터 객체)
- locales(가능한 모든 locale 값)

- locale(현재 locale 값)
- defaultLocale(기본적으로 설정된 locale 값)

 

 

 

getServerSideProps

getServerSideProps는 getStaticProps와는 달리 사용자가 페이지를 요청할 때마다 실행되는 함수이다.

 

동적인 데이터, 혹은 업데이트가 빈번한 데이터를 가져오는 데 적합하다.

 

매 요청 시마다 호출되므로 네트워크 비용이 많이 발생한다는 단점이 있지만, 동적인 데이터를 가져와서 사전 렌더링을 해야 하는 경우에 적합한 방식이다.

 

getServerSideProps의 사용법은 다음과 같다.

const 페이지_컴포넌트 = (props) => {
  // 페이지 컴포넌트
};

export default 페이지_컴포넌트;

export const getServerSideProps = async () => {
  // API 호출 혹은 파일 시스템 접근 등의 코드 작성
	
  return {
    props: {
      propsName: propsValue,
      // 여기서 페이지 컴포넌트에 넘겨줄 props를 리턴해야 함
    },
  };
};

 

 

getServerSideProps도 getStaticProps와 마찬가지로 redirect와 notFound 속성을 제공하며, context를 함수의 인자로 받아올 수 있다.

 

context에는 다음과 같은 값이 포함된다.

- pathname(현재 URL의 path)
- query(현재 URL의 쿼리 스트링 객체)
- locales(가능한 모든 locale 값)
- locale(현재 locale 값)
- defaultLocale(기본적으로 설정된 locale 값)
- req(HTTP request 객체)
- res(HTTP response 객체)
- err(HTTP error 객체)

 

 

 

getStaticPaths

getStaticPaths는 페이지가 동적 라우팅 + getStaticProps를 사용하는 경우에 정적으로 렌더링할 경로를 설정하기 위한 함수이다.

 

만약 동적 라우팅 + getStaticProps을 사용하는 페이지에서 getStaticPaths 함수를 사용하여 렌더링 경로를 설정하지 않는 경우에는 다음과 같은 오류가 발생하기 때문에 사용자가 페이지에 접근할 수 없다.

Server Error
Error: getStaticPaths is required for dynamic SSG pages and is missing for '/[동적 라우팅 페이지 경로]'.
Read more: https://err.sh/next.js/invalid-getstaticpaths-value

 

왜냐하면 프로젝트 빌드 시에 getStaticProps 함수가 실행되는 시점에는 동적 파라미터에 대한 정보를 받을 수 없기 때문에 미리 페이지를 생성할 수 없기 때문이다.

 

따라서 getStaticPaths 함수에 미리 런타임에 전달될 파라미터에 대한 정보를 전달하여 페이지가 정상적으로 pre-rendering되도록 해야 한다.

 

 

예시와 함께 코드를 보면서 이해해 보자.

const InfoPage = ({ info }) => {
  const { name, age, address } = info;
  return (
    <ul>
      <li><p>이름: {name}</p></li>
      <li><p>나이: {age}</p></li>
      <li><p>주소: {address}</p></li>
    </ul>
  );
};

export const getStaticProps = async ({ params }) => {
  const info = await getUserInfo(params.userId);

  return {
    props: { info },
  };
};

export const getStaticPaths = async () => {
  const userIds = await getUserIds();
  const paths = userIds.map((userId) => ({
    params: { userId },  // 반드시 파라미터 값은 string 타입이어야 함
  }));
  
  return { paths, fallback: false };
};

export default InfoPage;

 

예를 들어 /infos/[userId].js라는 동적 라우팅을 사용하는 페이지가 있다고 하자.

 

정상적으로 페이지를 렌더링하기 위해서는 다음과 같이 파라미터의 인자로 들어올 수 있는 모든 값을 고려하여 getStaticPaths 함수의 paths 속성 값으로 전달해야 한다.

export const getStaticPaths = async () => {
  const userIds = await getUserIds();
  const paths = userIds.map((userId) => ({
    params: { userId },  // 반드시 파라미터 값은 string 타입이어야 함
  }));
  
  return {
    paths,
    fallback: false,
  };
};

 

 

getStaticPaths 함수를 사용할 때는 paths 외에도 반드시 fallback 속성에 대한 값도 지정해야 한다.

 

fallback은 paths 속성에서 모든 지원되는 매개변수를 지정할 지 아니면 일부만 지정할 지에 대해 알려주는 속성이다.

 

fallback 속성은 boolean 값을 갖는다.

 

paths에 정의하지 않은 파라미터를 전달하는 경우 혹은 일부 파라미터를 누락한 경우, false인 경우에는 404 에러를 발생시킨다.

(pages 폴더 하위에 404.js라는 이름으로 에러 페이지를 생성한 경우에는 해당 페이지가 표출)

 

true인 경우에는 빌드 시에 페이지를 렌더링하지 않으며, 동적으로 페이지를 렌더링하여 보여준다.

 

 

 

getInitialProps

getInitialProps는 Next.js의 각 페이지 컴포넌트에서 사용할 수 있는 초기 props 값 지정 함수이다.

(페이지 컴포넌트가 아닌 일반 자식 컴포넌트에서는 사용 불가)

 

getStaticProps, getServerSideProps가 등장하기 전에 사용되던 Pre-rendering 함수이며, getServerSideProps와 거의 동일한 용도로 사용할 수 있다.

 

하지만 getInitialProps를 사용하게 되면 자동 정적 최적화(Automatic Static Optimization) 기능이 비활성화되는 문제가 있기 때문에 Next.js 9.3 버전 부터는 getInitialProps 대신 getStaticProps, getServerSideProps를 사용하는 것을 권장하고 있다.

 

자동 정적 최적화(Automatic Static Optimization)란?

Next.js에서 별도의 데이터 요구 사항이 없는 페이지의 Pre-rendering 여부를 자동으로 결정하는 기능

getInitialProps나 getServerSideProps 함수를 사용하는 경우가 아니라면 Next.js는 해당 페이지를 자동으로 Pre-rendering(정적 HTML 파일 생성)하여 성능을 최적화

 

자세한 내용은 아래 링크를 통해 확인할 수 있다.

 

Advanced Features: Automatic Static Optimization | Next.js

Next.js automatically optimizes your app to be static HTML whenever possible. Learn how it works here.

nextjs.org

 

 

getInitialProps도 마찬가지로 context를 함수의 인자로 받아올 수 있다.

 

context에는 다음과 같은 값이 포함되며, getServerSideProps의 값과는 약간의 차이가 있다.

- resolvedUrl(현재 URL의 path)
- query(현재 URL의 쿼리 스트링 객체)
- asPath(동적 파라미터를 포함한 실제 path 값)
- req(HTTP request 객체)
- res(HTTP response 객체)
- err(HTTP error 객체)

 

 

getInitialProps의 사용법은 다음과 같다.

/* 페이지 컴포넌트 내에서 사용시 */
const 페이지_컴포넌트 = ({ propsName }) => {
  // 페이지 컴포넌트
};

페이지_컴포넌트.getInitialProps = async (ctx) => {
  // API 호출 혹은 파일 시스템 접근 등의 코드 작성
	
  return {
    propsName: propsValue,
    // 여기서 페이지 컴포넌트에 넘겨줄 initial props를 리턴해야 함
    // 리턴된 props 객체는 pageProps라는 이름으로 _app.tsx의 페이지 컴포넌트에 전달됨
  };
};

export default 페이지_컴포넌트;


/* _app 컴포넌트 내에서 사용시 */
const App = ({ Component, pageProps, router }) => {
  return <Component {...pageProps} />;
};

App.getInitialProps = async (ctx) => {
  // API 호출 혹은 파일 시스템 접근 등의 코드 작성
	
  return {
    pageProps: {
      propsName: propsValue,
      // 여기서 페이지 컴포넌트에 넘겨줄 initial props를 리턴해야 함
    },
  };
};

export default App;

 

getStaticProps, getServerSideProps와는 달리 페이지 컴포넌트의 getInitialProps 프로퍼티 값으로 함수를 전달한다.

 

또한, getInitialProps는 일반 페이지 컴포넌트 뿐만 아니라 _app.tsx에서도 사용이 가능하다.

(getStaticProps, getServerSideProps는 _app.tsx에서 사용 불가)

 

최상위 컴포넌트인 _app.tsx에서 호출한 getInitialProps는 모든 하위 페이지 컴포넌트에 전달되며, 일반 페이지 컴포넌트와는 달리 _app.tsx에서는 pageProps로 props를 감싸서 리턴해야 한다. 

 

그리고 _app.tsx에서 getInitialProps를 호출하는 경우에는 하위 페이지 컴포넌트에서의 getInitialProps 호출은 무시된다.

 

 

 

기타 정리

- getInitialProps가 가장 먼저 실행되며, 이후 getStaticProps와 getServerSideProps가 실행된다.

 

- getInitialProps, getStaticProps, getServerSideProps는 동일한 페이지 컴포넌트 내에서 동시 사용이 불가능하다. 셋 중 하나를 골라서 적절하게 사용하도록 하자.

 

 

 

참고

 

Data Fetching: Overview | Next.js

Next.js allows you to fetch data in multiple ways, with pre-rendering, server-side rendering or static-site generation, and incremental static regeneration. Learn how to manage your application data in Next.js.

nextjs.org

 

What does `permanent` actually do for redirects? · Discussion #29556 · vercel/next.js

From the docs: I am wondering what does permanent actually do? permanent if the redirect is permanent or not. This was not really helpful from the official doc.

github.com