Next.js ^9.3 Data fetching

IT공부중·2020년 6월 8일
1

Next

목록 보기
9/10
post-thumbnail

9.2 버전까지는 getInitialProps로 퉁쳤던 ssr data fetching이 9.3버전부터 세분화 되었다. 오늘 Next에 TS를 써봐야지 하고 보다가 추가 된 것이 있어서 공부하며 적어본다.

일단

  • getStaticProps : 빌드 타임 때 data fetch
  • getStaticPaths : data에 기반하여 pre-render할 동적 라우팅을 적어주어라.
  • getServerSideProps: 각각의 요청마다 data를 fetch한다.

로 세분화 되었다.

getStaticProps (Static Generation)

이것은 빌드 타임 때 정적인 페이지를 생성하게 한다. page에서만 사용할 수 있으며, build 할 대 getStaticProps로 리턴한 props를 기반으로 page를 미리 렌더링 해놓는다. 따라서 무척 빠르게 페이지를 띄워준다.

export async function getStaticProps(context) {
  return {
    props: {}, // will be passed to the page component as props
  }
}

이런식으로 사용할 수 있으며, context에는 params와 preview에 관련된 내용들이 들어있다.
주로 사용할 것은 props인데 dPfmfemfdj [id].js 라고 만들어 놓은 페이지면 {id: ...}과 같은 것이 들어있을 것이다. getStaticPaths와 같이 사용하면 유용하게 사용할 수 있다.

// 포스트는 getStaticProps에 의해 build Time에 채워질 것이다.
function Blog({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li>{post.title}</li>
      ))}
    </ul>
  )
}

// 이 함수는 서버사이드에서 빌드 타임에 불러진다.
// 클라이언트 사이드에서 불러지길 원하지 않는다면 이것을 사용할 수 있다.
export async function getStaticProps() {
  const res = await fetch('https://.../posts')
  const posts = await res.json()

  // {props: {...}} 형태로 반환해야 하며 build time에 `posts`로 받을 수 있다. 
  return {
    props: {
      posts,
    },
  }
}

export default Blog

그러면 getStaticProps는 언제 사용해야할까?

  1. 데이터가 빌드 타임에 페이지에 사용되어야 할 때(사용자의 요청을 받지 않고)
  2. headless한 cms로부터 데이터가 올 때.
  3. 유저와 상관 없이 퍼블릭한 data를 캐쉬할 수 있는 데이터
  4. SEO 등의 이슈로 인해 빠르게 미리 렌더링 해야만 하는 페이지. getStaticProps는 HTML과 JSON파일을 모두 생성해 두기 때문에, 성능을 향상시키기 위해 CDN 캐시를 하기 쉽다.

여기에 TypeScript를 적용하려면 다음과 같이하면 된다.

import { GetStaticProps } from 'next'

export const getStaticProps: GetStaticProps = async (context) => {
  // ...
}

next에 내장되어있는 GetStaticProps를 type으로 주면된다.
그리고 return 해준 값을 컴포넌트에서 받아 쓰면 된다.

import { GetStaticProps } from 'next';
import Link from 'next/link';

import { User } from '../../interfaces';

type Props = {
  items: User[];
};

const WithStaticProps = ({ items }: Props) => (

);

export const getStaticProps: GetStaticProps = async () => {
  // Example for including static props in a Next.js function component page.
  // Don't forget to include the respective types for any props passed into
  // the component.
  const items: User[] = await (
    await fetch('http://localhost:3000/api/users')
  ).json();
  return { props: { items } };
};

export default WithStaticProps;

이런식으로 하면 Props에서 받아서 사용할 수 있다.
세부적인 상황으로 유의해야할 점은
getStaticProps는 빌드 타임에 한번만 실행 된다는 것이다. 빌드 타임에 실행 되고 정적 HTML을 만들어 내기 때문에 쿼리 매개변수나 HTTP 헤더 등 요청 시간에만 사용할 수 있는 데이터는 받지 않는다.

그리고 서버측에서만 실행된다는 것에 유의해야한다. 클라이언트쪽에서는 실행되지 않을 것이고 js로 만들어지지도 않는다. 미리 HTML을 만들어 놓는 것이기 때문에 동적으로 데이터를 가져올 수 없다.

그리고 getStaticProps는 페이지에서만 사용할 수 있다. 다른 컴포넌트 등에서는 사용할 수 없다.
그리고 개발 모드에서는 매번 요청한다는 것에 유의해야한다. 실제로 빌드를 해버리면 첫 요청 한번에 의한 정적인 HTML이 생성 되기 때문에 동적으로 데이터를 바꿀 수 없다.

getStaticPaths (Static Generation)

만약에 getStaticProps에서 동적인 라우팅을 필요로한다면 HTML 빌드타임에 렌더 해놓은 paths의 lists가 정의 되어있어야한다.

getStaticPaths는 다음과 같이 사용할 수 있다.

export async function getStaticPaths() {
  return {
    paths: [
      { params: { ... } } // See the "paths" section below
    ],
    fallback: true or false // See the "fallback" section below
  };
}```


paths키는 paths가 미리 렌더링 될때 결정된다. 예를 들어 `pages/posts/[id].js` 같이 dynamic routes를 설정해놓았으면 getStaticPaths에는 다음과 같이 리턴해야한다.

```js
return {
  paths: [
    { params: { id: '1' } },
    { params: { id: '2' } }
  ],
  fallback: ...
}

그러면 Next.js가 정적으로 posts/1, posts/2와 같은 페이지들을 pages/posts/[id].js 컴포넌트 에서 사용할 수 있도록 빌드타임에 생성해놓을 것이다.

params는 다음과 같이 작성되어야한다.
만약 pages/posts/[postId]/[commentId] 라면 params에는 postId 와 commentId가 포함 되어야한다.
만약 페이지이름이 여러 라우트에 매칭이 되기를 원한다면 예를 들어 pages/[...slug] 같이..
그러면 params는 slug라는 배열을 순서대로 포함된것에 매칭된다. 예를들어 array가 ['foo','bar']이라면 Next는 /foo/bar이라는 페이지를 정적으로 생성할 것이다.

fallback 키도 필수이다.
만약 fallback이 false라면 getStaticPaths에 의해 반환되지 않은 모든 경로는 404페이지가 된다.
미리 렌더링의 해놓은 경로의 수가 적으면 false로 해놓기 좋다. 새로운 페이지가 자주 추가 되지 않을 때 유용한 기술이다.
만약 더 많은 양의 데이터의 아이템들을 추가하여 새로운 페이지를 렌더하고 싶으면 다시 빌드해야한다.

users/101 ~ users/105 까지 미리 렌더링을 해놓은 상태에서 106으로 이동하게 되면 다음과 같은 404페이지가 나타난다.

fallback이 true일 경우에는 빌드시 생성되지 않은 경로는 404 페이지가 되지 않는다 대신에 fallback page를 보여준다. fallback은 다음과 같이 만들어 둘 수 있다.

// pages/posts/[id].js
import { useRouter } from 'next/router'

function Post({ post }) {
  const router = useRouter()

  // If the page is not yet generated, this will be displayed
  // initially until getStaticProps() finishes running
  if (router.isFallback) { // 잠시 이 창이 뜨게 된다. 그리고 나서 에러 페이지로 바뀜.
    return <div>Loading...</div> 
  }

  // Render post...
}

// This function gets called at build time
export async function getStaticPaths() {
  return {
    // Only `/posts/1` and `/posts/2` are generated at build time
    paths: [{ params: { id: '1' } }, { params: { id: '2' } }],
    // Enable statically generating additional pages
    // For example: `/posts/3`
    fallback: true,
  }
}

// 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

제대로 확인 해보기 위해서 build 한 후 배포버전으로 실행해보았다.

npm run build
를 실행할 경우

위과 같은 페이지들이 미리 생성 되는 것을 볼 수 있다.
fallback을 false로 했을 경우에는 Next에서 미리 만들어 놓은 404 페이지가 뜨는 반면, true일 경우에는 fallback으로 정한 페이지가 잠시 나왔다가, 해당 컴포넌트만 빈 채로 페이지가 렌더링 됐다.

fallback:true는 다음과 같을 때 좋다.
만약 앱이 data에 의존하는 엄청나게 많은 static pages가 필요할 때이다. 모든 프로덕트들의 pages가 pre-render 되길 원하지만 그러면 빌드가 엄~~~청 오래 걸릴 것이다.

대신에 fallback: true를 사용하면 된다. 약간의 subset pages들만 생성 해놓고 다른 페이지들은 누군가가 페이지 요청을 보냈을 때 fallback page(loading 이라던가)를 잠시 보여준 다음에 요청된 데이터를 getStaticProps가 page 렌더를 끝내고 나면 보여줄 것이다. 그 이후로는 모든 사람이 같은 페이지를 요청해도 미리 렌더링된 페이지를 요청해준다.
이를 통해 사용자는 빠른 빌드와 정적 생성의 이점을 보존하면서 항상 빠른 경험을 할 수 있다.

그러니깐 즉 쿠팡이나, 아마존 같이 엄청나게 많은 상품들의 페이지들을 다 build 해놓기에는 엄청나게 오래 걸릴 것이라는 거다. 그래서 몇 개의 대표적인 페이지들만 pre-render 해놓고 나머지들은 첫번째 요청이 올 시에 생성이 된다는 것이다. 그 이후로는 pre-render 된 것 같이 빠르게 사용할 수 있다.

이것은 getStaticProps와 같이 사용해야 하고, 서버사이드 빌드 타임에만 동작하고 페이지에서만 사용할 수 있다. 그리고 개발모드에서는 매번 요청된다.

TypeScript를 적용하기 위해서는 다음과 같이 하면 된다.

import { GetStaticPaths } from 'next'

export const getStaticPaths: GetStaticPaths = async () => {
  // ...
}

getServerSideProps (Server-side-Rendering)

getServerSideProps를 사용하면 각각의 페이지에서 요청마다 data를 사용해서 pre-render 할 것이다.
다음과 같이 용할 수 있다.

export async function getServerSideProps(context) {
  return {
    props: {}, // will be passed to the page component as props
  }
}

context 에는 다음의 key를 가지는 object이다.
params : 앞에서 설명했던 것과 같음.
req : The Http IncomingMessage object
res : The HTTP response object
query: The query string
preview, previewData

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 에서 fetch와 같은 ajax를 사용해서 data를 가죠오고 return 해주면 Page 컴포넌트에서 사용할 수 있다. 기존에 있던 getInitialProps가 서버와 클라이언트 둘다에서 실행 되었다면 getServerSideProps는 서버에서만 실행되는 것이라고 생각하면된다.(getInitialProps는 뭔가 deprecated 될 것 같다.)

getServerSideProps는 언제 사용해야 할까?
만약에 요청 때 fetched 되는 data로 pre-render를 해야할 때 사용할 수 있다. 각각의 요청마다 결과를 만들어내야되고 결과는 캐쉬될 수 없기 떄문에 getStaticProps 보다는 느리다. 만약 pre-render 데이터가 필요하지 않다면 next에서 추천하는 SWR 같은 것을 사용해보라고 한다. 그냥 client-side-rendering이다. useEffect를 사용해서 한다던지 추천하는 방식을 해서 하던지 상관 없을 것 같다.

TypeScript를 적용하기 위해서는 다음과 같이 해주면 된다.

import { GetServerSideProps } from 'next'

export const getServerSideProps: GetServerSideProps = async (context) => {
  // ...
}

getServerSideProps는 서버사이드에서만 실행됩니다. 브라우저에서는 실행되지 않습니다. page가 getServerSideProps를 사용하면
페이지로 직접 요청을 보냈을 때 요청시간에 getServerSideProps가 작동하고, page는 return된 props로 pre-render 될 것입니다.
만약 next/link 또는 next/router를 통해 client/side로 요청을 보내면 Next.js는 getServerSideProps가 실행되고 있는 서버로 API request를 보낼 것 입니다. 이것은 getServerSideProps의 실행결과를 포함한 JSON을 리턴하고 JSON은 page를 렌더하는데에 사용될 것입니다.

이 모든 작업은 Next가 알아서 다 해주기 때문에 추가적으로 설정할 필요가 없다.
getServerSideProps도 마찬가지로 page에서만 사용하여야 합니다.

정리

getStaticProps와 getStaticPaths는 같이 쓰는 것이 좋으며 미리 정적인 페이지를 만들어줘서 빠른 렌더링을 도와준다. 유저에 따라 바뀌진 않지만 데이터에 의해 미리 렌더링 돼야 할 때 사용. 빌드 타임에만 실행되기 때문에 빌드 이후 새로운 데이터가 추가되면 재빌드 해줘야함.

getServerSideProps는 ssr을 위해 사용 되며 각 요청마다 새로 실행되고 rendering하기 때문에 getStaticProps보다는 느리다. 하지만 데이터가 계속해서 변해 새로운 데이터로 render해야할 때 사용하면 좋다.

세 가지 함수 모두 pages에서만 사용할 수 있다!

profile
3년차 프론트엔드 개발자 문건우입니다.

1개의 댓글

comment-user-thumbnail
2020년 12월 14일

getStaicProps가 개발모드에는 페이지 요청시 항상 실행된다는 부분에서 무릎을 탁 치고갑니다.
이해를 잘못한건지 헤매다가 이부분에서 의문이 다 풀렸네요!

답글 달기