Next.js에서에서 각 페이지는 pages 디렉토리의 .js, .jsx, .ts or .tsx 파일들로부터 추출된 React Component이다.

예: 만약 pages/about.js 페이지를 생성한다면 그건 아래와 같은 모습일거고, /about에 접근할 수 있다.

function About() {
  return <div>About</div>
}

export default About

Pages with Dynamic Routes

Next.js는 동적라우트 할 수 있는 페이지를 제공한다. 만약 pages/posts/[id].js파일을 생성한다면 posts/1, posts/2 등에 접근할 수 있다.

Pre-rendering

Next.js는 디폴트로 매 페이지를 pre-rendering 한다. 즉 Next.js는 client-side javascript에 의해 모든것이 완료되는 대신 미리 각 페이지 HTML을 생성한다. 이런 Pre-rendering은 app의 퍼포먼스와 SEO(Search engine optimization)에 더 좋은 결과를 만든다.

생성된 HTML은 해당 각 페이지의 최소한의 필수적인 js code와 연관된다. 그래서 브라우저가 로드될 때 그 필수적인 js code가 실행이 되고 해당 페이지는 완전한 상호작용을 할 수 있게 된다.(이런 과정을 hydration이라 한다.)

Two forms of Pre-rendering

Next.js는 두가지 종류의 pre-rendering이 있다: Static GenerationServer-side Rendering. 두 개의 차이는 언제 HTML을 생성하는데 있다.

  • Static Generation (Recommended): HTML을 빌드시에 생성한다 그리고 매 요청이 있을 때마다 그 HTML은 재사용된다.
  • Server-side Rendering: HTML은 매 요청시에 생성된다.

중요한건, 개발자 어떤 pre-rendering을 할지 선택할 수 있다. 그래서 개발자는 대부분의 페이지를 Static Generation으로 그리고 그 외는 Server-side Rendering으로 하는 "hybrid" Next.js app을 개발 할 수 있다.

Static Generation는 Server-side Rendering 보다 퍼포먼스적으로 더 좋다. 정적으로 생성된 페이지는 성능개선을 위한 특별한 설정없이 CDN으로 캐시되기 때문이다. 하지만 Server-side Rendering만 사용할 수 밖에 없는 경우도 있다.

Client-side Rendering 또한 Static-Generation 이나 SSR과 함께 사용가능하다. 즉 어떤 페이지는 전체적으로 Cient-side js에 의해 렌더될 수 있다는 뜻이다. 좀 더 자세한 정보는 Data Fetching 문서를 살펴봐라.

만약 Static Generation을 사용한다면, 그 페이지는 빌드시에 HTML이 생성된다. 이말은 운영환경에서 HTML페이지가 next build 가 실행될때 생성되다는 뜻이다. 이 HTML은 매 요청시에 재사용될 것이고 CDN의 의해 캐시된다.

Next.js에서는 정적페이지를 데이터가 있게 혹은 없게 생성할 수 있다. 한번 각 경우를 살펴보자.

Static Generation without data

Next.js는 데이터 fetching없이 Static Generation하는 것이 디폴트다. 아래 예가 있다.

function About() {
  return <div>About</div>
}

export default About

이 페이지는 pre-render를 위해 어떤 외부 data도 가져올 필요가 없다. 이런경우 Next.js는 빌드시에 해당 HTML파일을 생성한다.

Static Generation with data

어떤 페이지들은 pre-rendering 시에 외부 data가 필요하다. 이런경우 Next.js가 제공하는 특별한 함수를 사용할 수 있는데, 아래 함수중 한개 혹은 두개 모두 사용이 필요할 수 있다.

  1. 만약 페이지 content가 외부 data에 종속된 경우: getStaticProps를 사용해라
  2. 만약 페이지 paths가 외부 data에 종속된 경우: getStaticPaths를 사용해라. (이런 경우 보통 getStaticProps도 같이 사용해야한다.)

Scenario 1: 만약 페이지 content가 외부 data에 종속된 경우

: 만약 블로그 페이지가 CMS(content management system)로부터 블로그 포스트 리스트를 가져와야할 때

// TODO: Need to fetch `posts` (by calling some API endpoint)
//       before this page can be pre-rendered.
function Blog({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li>{post.title}</li>
      ))}
    </ul>
  )
}

export default Blog

pre-render시 이 데이터를 가져오기위해 Next.js는 해당 파일에 getStaticProps라는 async함수를 export 하게 해준다. 이 함수는 빌드시에 호출된다. 그리고 pre-render때 가져온 데이터를 해당 페이지의 props로 넘겨준다.

function Blog({ posts }) {
  // Render posts...
}

// This function gets called at build time
export async function getStaticProps() {
  // Call an external API endpoint to get posts
  const res = await fetch('https://.../posts')
  const posts = await res.json()

  // By returning { props: { posts } }, the Blog component
  // will receive `posts` as a prop at build time
  return {
    props: {
      posts,
    },
  }
}

export default Blog

getStaticProps가 어떻게 동작하는지 알고 싶다면 Data Fetching 문서를 참고해라.

Scenario 2: 만약 페이지 paths가 외부 data에 종속된 경우

Next.js는 dynamic routes로 페이지를 생성할 수 있게 할 수 있다. 예를 들면 pages/posts/[id].jsid를 기준으로 한 블로그 포스트를 보여준다. 따라서 post/1에 접근하면 id:1로 된 블로그 포스트를 보여줄 수 있다.

하지만 빌드타임에 어떤 id인가를 판단하는건 외부정보에 종속된다.

: 만약 db에 id:1로 된 블로그포스트를 하나 올렸다고 하자. 그럼 빌드시 posts/1 만 pre-render 되길 원할 것이다.

나중에 만약 id:2의 블로그 포스트가 추가된다면, posts/2도 역시 pre-render되길 원할 것이다.

그럼 pre-render되는 페이지들의 paths는 외부 data에 종속되는 셈이다. Next.js는 이런 경우 해당페이지(pages/posts/[id].js)에 getStaticPaths라 불리는 async 함수를 export 할 수 있게 제공해준다. 이 함수는 빌드시에 호출되고 pre-render 되는 paths를 특정할 수 있게 해준다.

// This function gets called at build time
export async function getStaticPaths() {
  // Call an external API endpoint to get posts
  const res = await fetch('https://.../posts')
  const posts = await res.json()

  // Get the paths we want to pre-render based on posts
  const paths = posts.map((post) => ({
    params: { id: post.id },
  }))

  // We'll pre-render only these paths at build time.
  // { fallback: false } means other routes should 404.
  return { paths, fallback: false }
}

또한 pages/posts/[id].jsgetStaticProps를 export할 필요가 있다. 그래야만 해당 id 포스트 페이지의 대한 data를 가져올 수 있고 pre-render 시에 사용할 수 있다.

function Post({ post }) {
  // Render post...
}

export async function getStaticPaths() {
  // ...
}

// This also gets called at build time
export async function getStaticProps({ params }) {
  // params contains the post `id`.
  // If the route is like /posts/1, then params.id is 1
  const res = await fetch(`https://.../posts/${params.id}`)
  const post = await res.json()

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

export default Post

getStaticPaths의 동작을 좀 더 알고싶다면 Data Fetching 문서를 참조해라.

When should I use Static Generation?

보통 Static Generation을 가능한 권장한다. 왜냐면 이경우 매 요청시마다 페이지를 render하는 SSR 보다 훨씬 빠르기 때문이다.

Static Generation 은 아래 경우들에 사용 할 수 있다.

  • Marketing pages
  • Blog posts
  • E-commece product listings
  • help and documentation

스스로 물어보기: "이 페이지는 사용자 요청 에 pre-render될 수 있을까?" 만약 그러면 Static Generation을 택해라.

반면 Static Generation이 좋지 않은 경우가 있다. 만약 사용자 요청전에 미리 페이지를 pre-render할 수 없는 경우가 그렇다. 만약 해당 페이지의 정보가 자주 업데이트 되고 그 페이지 content가 매 요청 시마다 변경되는 경우가 그런 경우다.

이런 경우 다음 중 하나를 선택할 수 있다:

  • Static Generation 과 Client-side Rendering: 페이지의 특정 정보는 pre-render를 스킵할 수있다. 그리고 client-side js를 통해 그 특정 정보를 채운다. 만약 이런 접근방식을 더 알고 싶다면 Data Fetching 문서를 참고해라.

  • Server-side Rendering: Next.js는 매 요청 시마다 페이지를 pre-render한다. 이건 쫌 느릴거다. 왜냐면 CDN으로 캐시할 수 없으니까. 그렇지만 pre-rendered 페이지는 항상 최신정보를 가지고 있을 것이다.

Server-side Rendering

SSR 혹은 Dynamic Rendering으로 불리기도 한다.

만약 Server-side Rendering을 사용한다면 HTML페이지는 매 요청시마다 생성된다.

SSR을 사용하기 위해서는 getServerSideProps 라는 async 함수를 export 할 필요가 있다. 이 함수는 서버에서 매 요청시에 호출된다.

예를 들어 빈번히 data가 업데이트 되는 페이지를 pre-render한다 가정하자. 이때 getServerSideProps를 쓸 수 있고 이 함수는 data를 가지고 와서 아래처럼 Page에 전달한다.

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

getServerSidePropsgetStaticProps와 비슷하다. 차이점은 getServerSideProps는 빌드타임이 아닌 매 요청시에 실행되다는 점이다.

getServerSideProps의 동작이 어떻게 되는지 알고 싶다면 Data Fetching문서를 참조해라.

Summary

Next.js의 두가지 방식의 pre-render에 대해 이야기해 보았다.

  • Static Generation(권장): 빌드시에 HTML을 생성하고 그건 매 요청시에 재사용된다. Static Generation을 사용하는 페이지를 만드려면 가져올 데이터가 없는경우는 그냥 컴포넌트를 export하면 되고, 외부 데이터에 종속된 페이지의 경우 getStaticProps를 쓰면된다. 그리고 필요에 따라 getStaticPaths도 필요할 수 있다. 사용자 요청전에 pre-render 할 수 있는건 굉장히 좋은 거고 Client-side 렌더링과 추가정보를 가져오는데 함께 사용할 수 있다.

  • Server-side Rendering: 매요청마다 HTML이 생성된다. SSR을 하기 위해서는 getServerSideProps를 export해야한다. SSR은 Static Generation보다 성능면에서 더 느리다. 그러니까 반드시 필요한 경우만 사용해라.

Learn more

다음 정보를 읽는 것을 추천한다.

  • Data Fetching
  • Preview Mode
  • Routing
  • TypeScript
profile
🌃브로콜리한 개발자🌟

0개의 댓글