[Next.js] Data Fetching & Rendering

JISEUNG·2023년 3월 12일
6

Next.js

목록 보기
1/5
post-thumbnail

Next.js는 페이지를 어떻게 렌더링할까? Data fetching을 할 때, 사용에 따라 다양한 방식으로 렌더링할 수 있다.

Pages

Pre-rendering

Next.js는 기본적으로 모든 page를 pre-render한다.

  • 즉, 각 페이지의 HTML을 미리 만들어놓는다. (SEO에 유리하다.)

  • 브라우저에 페이지가 로드되면 그때 JavaScript 코드를 실행해서 interactive해진다.(hydration)

  • Pre-rendering의 2가지 양상

    1. Static Generation (Recommended)
      • 빌드 타임에 HTML이 생성되고 매 요청마다 재사용된다.
      • 성능상 더 좋다. 정적으로 생성되어 별 다른 설정없이 CDN으로 캐시될 수 있다.
    2. Server-side Rendering
      • 매 요청마다 HTML이 생성된다.
  • Client-side data fetching

    • 어느 방식으로 Pre-rendering을 하든 클라이언트 사이드에서 데이터 패칭이 가능하다.
    • 페이지의 특정 영역은 완전히 클라이언트 사이드 자바스크립트로 렌더링할 수 있다.

Static Generation

Q. 유저가 요청하기 전에 페이지를 pre-render할 수 있는가?

  • 빌드타임(next build)에 HTML 페이지가 생성된다.

    • 매 요청마다 재사용된다.
    • CDN에 캐시될 수 있다.
  • Static Generation without fetching data

    • 빌드 타임에 페이지마다 단일한 HTML 파일을 생성한다.
  • Static Generation with fetching data

    • getStaticProps : 페이지 content가 외부 데이터에 의존
      • 빌드 타임에 호출된다.
      • params를 인자로 받을 수 있다.
    • getStaticpaths : 페이지 path가 외부 데이터에 의존. 보통 getStaticProps와 함께 사용된다.
      • 빌드 타임에 호출해서 pre-render할 path를 알게 해준다.
      • paths, fallback을 반환한다.
  • 가능하면 항상 쓰자. 페이지가 단 한번만 생성되고 CDN에서 받을 수 있어서 서버가 매 요청마다 렌더링하게 하는 것보다 빠르다.

  • ex. 블로그 포스트, 상품 목록 등

  • 데이터가 너무 자주 바뀌고, 매 요청마다 바뀐다면 다음을 사용하는 것을 고려해보자

    • Static Generation with Client-side data fetching
      • pre-rendering에서 일부만 처리하고, 데이터 패칭은 Client-side에서 처리하자
    • Server-Side Rendering
      • 매 요청이 들어올 때마다 페이지를 pre-render하게 한다.
      • CDN에 캐시되지 않아 느릴 수는 있지만 항상 최신 데이터를 보장한다.

Server-side Rendering

Q. 페이지가 너무 많이 바뀌는 데이터를 필요로 하는가?

  • page가 Server-side 렌더링을 한다면, HTML 페이지는 매 요청마다 생성된다.

  • getServerSideProps를 export해야 한다.

    • 매 요청마다 서버에서 호출된다.
    • Static Generation보다 느려질 것이다. 정말 필요할 때만 사용하자.

Data Fetching

Next.js에서 Data fetching을 할 때, 사용에 따라 다양한 방식으로 콘텐츠를 렌더링할 수 있다.

getServerSideProps (SSR: Server-Side Rendering)

페이지에서 getServerSideProps를 export하면 페이지는 server side rendering 된다.

export async function getServerSideProps(context) {
  return {
    props: {}, // will be passed to the page component as props
  }
}
  • props로 page에 아무거나 넘겨줄 수 있다. hydrated
  • Server-side에서만 실행된다.

언제 실행될까?

  • Request directly : 페이지 요청 하는 시점에 실행된다. 페이지는 리턴된 props로 pre-render된다.

  • Client-side transitions : next/link, next/router를 통해 서버로 API 요청을 보내면 그 때 실행된다.

  • JSON을 반환하고, 반환된 props는 page를 pre-render하는데 사용된다.

  • 페이지에서만 사용할 수 있다.

  • 반드시 standalone function으로 export해야 한다.

    • 페이지 컴포넌트의 property로서는 동작하지 않는다.

언제 사용해야할까?

  • 데이터가 요청 시간에 패치되어야 하는 페이지를 렌더링할 때 사용해야 한다.
  • 그러고 싶지 않다면, client side나 getStaticProps에서 데이터를 패치하자.
  • 가끔 바뀌는 데이터를 패치하고, 최신 데이터로 페이지를 그리고 싶을 때 유용하다.

Client side 데이터 패치와 사용하기

  • 페이지에 자주 업데이트되는 데이터가 포함되어 있으며, pre-render할 필요가 없다면 클라이언트 사이드에서 데이터를 패치하면 된다.

Caching

  • Cache-Control을 사용해 getServerSideProps에서 응답을 캐싱할 수 있다.
export async function getServerSideProps({ req, res }) {
  res.setHeader(
    'Cache-Control',
    'public, s-maxage=10, stale-while-revalidate=59'
  )

  return {
    props: {},
  }
}

getStaticProps (SSG: Static-Site Generation)

페이지에서 getStaticProps를 export하면 페이지는 빌드 시 pre-render된다.

export async function getStaticProps(context) {
  return {
    props: {}, // will be passed to the page component as props
  }
}
  • return된 props를 사용해서 빌드 타임에 페이지를 pre-render한다.
  • 렌더링 타입에 관계 없이 모든 props는 페이지로 전달 될 수 있고 초기 HTML의 client-side에서 볼 수 있다.
    - 이 점이 올바른 hydrate를 가능하게 한다.
  • client에서 보면 안되는 민감한 정보를 props로 전달하지 않아야 한다.

언제 사용할까?

  • 페이지를 렌더링하는데 필요한 데이터가 빌드 타임에(유저가 요청하기 전에) 사용 가능 할 때
  • 데이터를 headless CMS에서 가져올 때
  • 페이지가 SEO를 위해 pre-render되어야 하고, 매우 빨라야 할 때. getStaticProps는 HTML과 JSON 파일을 생성해 CDN에 캐시되어 성능을 높여줄 것이다.
  • 데이터가 public하게 캐시될 수 있을 때. 미들웨어를 사용해 path를 재작성함으로써 특정 상황에서 이 조건을 우회할 수 있다.

언제 실행될까?

  • server에서만 실행된다.

  • next build 중에 실행된다.

  • fallback: true 사용 시 백그라운드에서 실행된다.

  • fallback: blocking 사용 시 초기 렌더 이전에 호출된다.

  • revalidate 사용 시 백그라운드에서 실행된다.

  • revalidate() 사용 시 호출할 때 백그라운드에서 실행된다.

  • ISR(Increment Static Regeneration)과 결합되면 stale한 페이지가 fresh한 페이지로 갱신될 때 백그라운드에서 실행된다.

  • 정적 HTML을 생성할 때 query parameter나 HTTP 헤더 등의 요청에 접근할 수 없다. 접근하고 싶다면 미들웨어를 사용하는 것을 고려해야 한다.

CMS로부터 데이터 패치하기

  • getStaticProps() 내에서 데이터를 패치하고, 결과를 props로 전달한다.

server-side 코드를 직접 쓰기

  • database 쿼리를 직접 가져다 쓸 수 있다.

HTML과 JSON을 모두 정적으로 생성한다.

  • 빌드 타임에 pre-render하면서 HTML 파일을 정적으로 생성하고, getStaticProps 실행 결과를 JSON 파일로 생성해 저장해둔다.
  • 생성된 JSON 파일은 next/link 또는 next/router 을 통한 client-side 라우팅 시 사용된다. getStaticProps를 사용해 pre-render된 페이지로 이동할 때, Next.js는 빌드타임에 생성된 이 JSON파일을 fetch해 page 컴포넌트의 props로 사용한다. 즉, client-side 페이지 이동이 getStaticProps를 호출하지 않으며 export된 JSON을 사용한다는 뜻이다.
  • ISR 사용 시, getStaticProps는 백그라운드에서 실행되어 client-side 이동 시 필요한 JSON을 생성해둔다. 같은 페이지에 대해 다중 요청이 있는 것을 볼 수 있는데, 이건 의도된 거고 성능에 영향을 미치지 않는다.

어디에서 사용할까

  • page에서만 export할 수 있다. _app이나 _document 혹은 _error 에서는 export할 수 없다. 왜냐면 React가 페이지를 렌더하기 전에 필요한 모든 데이터를 가지고 있어야 하기 때문
  • 반드시 standalone function으로 export해야 한다.
    - 페이지 컴포넌트의 property로서는 동작하지 않는다.

Appendix

  • 개발 모드(next dev)에서는 매 요청마다 호출된다.
  • preview mode에서는 빌드 타임 대신 요청 시간에 정적 생성 및 페이지 렌더를 우회할 수 있다.

getStaticPaths

페이지가 Dynamic Routes를 갖고 있고, getStaticProps를 사용하고 있다면 정적으로 생성할 path의 목록을 미리 정의해줘야 한다.

export async function getStaticPaths() {
  return {
    paths: [{ params: { id: '1' } }, { params: { id: '2' } }],
    fallback: false, // can also be true or 'blocking'
  }
}
  • Next.js는 getStaticPaths가 명시한 모든 path에 대해 pre-render한다.

언제 사용할까?

  • Dynamic route를 사용하며 정적으로 페이지를 pre-render할 때
  • 데이터를 headless CMS에서 가져올 때
  • 데이터를 데이터베이스에서 가져올 때
  • 데이터를 파일시스템에서 가져올 때
  • 데이터를 public하게 캐시할 수 있을 때 (유저 특정이 아니라)
    • 페이지가 SEO를 위해 pre-render되어야 하고, 매우 빨라야 할 때. getStaticProps는 HTML과 JSON 파일을 생성해 CDN에 캐시되어 성능을 높여줄 것이다.

언제 실행될까?

  • production에서 빌드할 때만 실행된다. 런타임에는 실행되지 않는다.
  • getStaticPropsnext build때 빌드에서 리턴된 path로 실행된다.
  • getStaticPropsfallback: true 일 때 백그라운드에서 실행된다.
  • getStaticPropsfallback: blocking일 때 초기 렌더링 전에 호출된다.

어디에서 사용할까?

  • getStaticPathsgetStaticProps와 함께 사용되야 한다.
  • getStaticPathsgetServerSideProps와 함께 사용할 수 없다.
  • Dynamic Route로부터 export할 수 있다.
  • page 컴포넌트에서만 export할 수 있다.
    • 반드시 export해야 한다.

Appendix

  • 개발 모드(next dev)에서는 매 요청마다 호출된다.
  • on-demand시 paths 생성 : paths에 대한 빈 배열을 리턴함으로써 모든 페이지에 대한 생성을 on-demand 때까지 지연할 수 있다.
    • 다양한 환경에 앱을 배포할 때 유용하다. 예를 들어 preview를 위해 언제든지 페이지를 생성함으로써 빌드를 빠르게 할 수 있다.
    • 수천개의 정적 페이지에 대해 유용하다.

Incremental Static Regeneration (ISR)

  • Next.js는 빌드 후에 정적 페이지를 생성하거나 갱신할 수 있게 해준다.

  • ISR이 페이지 기반으로 전체를 다시 빌드하지 않고도 정적 생성이 가능하도록 해준다.

  • ISR로 페이지가 많아도 정적인 페이지가 주는 이점을 누릴 수 있다.

  • experimental-edge 런타임과 호환되지 않는다. cache-control 헤더를 수동으로 설정해 stale-while-revalidate를 활용할 수 있다.

  • ISR을 사용하기 위해 getStaticPropsrevalidate prop을 추가하면 된다.

  • 빌드 타임에 pre-render된 페이지를 만드는 요청이 오면, 일단 캐시된 페이지를 보여준다.

    • 최초 요청 이후의 모든 요청은 revalidate amount 이내라면 캐시되어 즉시 처리된다.
    • revalidate amount 이후 요청에도 여전히 캐시된 (Staled) 페이지를 보여준다.
    • Next.js는 백그라운드에서 페이지를 재생성 한다. 성공하면 캐시를 invalidate하고 갱신된 페이지를 보여준다. 실패 시에는 이전 페이지를 그대로 보여준다.
    • path에 대한 요청이 생성되지 않으면 첫 요청에서 server render하고, 이후 요청에서 캐시에서 정적 파일을 가져온다. Vercel에서 ISR은 캐시를 글로벌하게 유지하고 rollbacks을 핸들링한다.
    • upstream 데이터 제공자가 기본적으로 캐싱을 가능하게 하는지 체크해라. 아마 disable해야 할 것이다. 그렇지 않으면 revalidation이 fresh한 데이터를 가져와 ISR 캐시를 갱신할 수 없을 것이다. 캐싱은 Cache-Control 헤더에서 리턴될 때 CDN에서 발생한다.

On-demand Revalidation

  • revalidate time 설정에 따라 모든 방문자는 그 시간에 같은 버전을 보게 된다. 캐시를 invalidate하는 유일한 방법은 그 시간 후에 누군가가 페이지를 방문하는 수 밖에 없다.
  • v.12.2.0부터 Next.js는 On-demand ISR을 지원해 수동적으로 특정 페이지를 갱신하게 해주는데, 다음의 경우에 좋다.
    • headless CMS로부터 생성되거나 갱신된 콘텐츠
      • Ecommerce 메타 데이터의 변경
    • getStaticProps 내부에서 revalidate를 특정하지 않아도 된다. revalidate를 제거하면 Next.js는 기본값(false, no revalidation)을 사용하고, revalidate()호출 시에만 revalidate한다.
    • 미들웨어는 On-demand ISR 요청을 실행시키지 않는다. revalidate하고 싶은 특정 path에서 revalidate() 호출해야 한다.

On-demand Revalidation 테스트

  • 개발모드에서 getStaticProps는 매 요청에서 발생한다.

에러 핸들링

  • getStaticProps 내에서 백그라운드 재생성 에러, 혹은 수동으로 설정한 에러가 발생하면 마지막으로 성공적으로 생성된 페이지를 보여준다. 차후 요청에서는 getStaticProps를 재시도한다.

Self-hosting ISR

  • ISR은 next start를 사용했을 때 self-hosted Next.js site에서 동작한다.
  • 기본적으로, 생성된 에셋은 각 pod의 메모리에 저장된다. 즉 각 pod은 정적 파일들에 대한 자체 복사본을 가진다. 특정 pod이 요청을 받을 때까지 Stale한 데이터가 표시될 수 있다.
    • 모든 pod의 일관성을 위해 in-memory 캐싱을 비활성화할 수 있다. 이렇게 하면 Next.js 서버에 파일 시스템의 ISR이 생성한 에셋만 활용하도록 알려준다. 공유 네트워크를 사용해 다른 컨테이너 간 같은 파일 시스템 캐시를 재사용할 수도 있다. 이 경우 .next 폴더의 캐시도 공유되고 재사용된다. isrMemoryCacheSize를 0으로 주면 되며, 이 경우 다중 pod이 동시에 같은 캐시를 갱신하려고 할 때 race condition에 유의해야 한다.

Client-side (CSR: Client-Side Rendering)

  • page가 SEO를 필요로 하지 않을 때, 혹은 페이지가 자주 갱신될 때 유용하다.

  • SSR과 다르게 컴포넌트 레벨에서 데이터를 패칭할 수 있다.

  • 페이지 레벨에서, 런타임에 데이터가 패치되고 페이지 콘텐츠는 데이터가 변함에 따라 갱신된다.

  • 컴포넌트 레벨에서, 데이터는 컴포넌트가 마운트될 때 패치되고 컴포넌트 콘텐츠는 데이터가 변함에 따라 갱신된다.

  • client-side 데이터는 페이지 로드 속도와 어플리케이션 성능에 영향을 미친다. 데이터는 캐시되지 않으며, 컴포넌트나 페이지가 마운트될 때 패치되기 때문이다.

  • 방법

    • useEffect를 사용하기
    • SWR로 패치하기
      • client-side에서 데이터를 패치하는 경우 캐싱, revalidation, focus tracking, refetching on intervals 등을 가능하게 하므로 권장된다.

Reference

Next.js Pages
Next.js Data Fetching

profile
Frontend Web Developer

1개의 댓글

comment-user-thumbnail
2023년 3월 22일

필요했던 게 다 있어요! 너무 맛있어요!

답글 달기