Next JS 13 적용해보기 - 데이터 불러오기 Data Fetching 편

2023. 8. 15. 14:37Library,Framework

728x90
2023.03.05 - [Library, Framework] - Next JS 13 적용해 보기 - App Routing 편

NEXT 13 Data Fetching

 

이번 Next JS 13 적용해 보기 시리즈에서는 서버에서 데이터를 불러오는 data fetching 방법에 대해서 알아보도록 하겠습니다. 

Next JS 13 이전 버전에서는 getStaticProps이나 getServerSideProps라는 메서드를 사용하면 data fetching 및 사전에 각 페이지의 HTML을 생성해 두는 Pre-rendering이 가능했습니다.

 

사실 이름만 복잡하지 막상 사용해 보면 크게 사용하기 힘든 점은 없었으나 처음 Next JS를 접했을 때는 어렵게 느껴질 수도 있을 법만 한 것들이기도 하고 React 18에 Server Component와 Client Component가 각각 분리되면서 함께 변화를 준 것으로 생각됩니다.

 

Next JS 13 이전 버전에서 Data Fetching 및 Pre-rendering 하는 방법이 궁금하다면 필자가 전에 작성했던 글을 참고해 주시길 바랍니다.

 

 

[NEXT.js] 넥스트 JS를 배워보자 1편 - Pre Rendering

예전부터 꼭 배워보고 싶은 Next JS 프레임워크에 드디어 입문했다. 여태까지 관리자 페이지나 랜딩페이지를 만들다 보니 CSR(Client Side Rendering)로도 충분했지만 현재 SEO과 원할하게 이루어져야 하

mingeesuh.tistory.com

 

바쁘신 분들을 위해 요약을 해보자면 Next JS에서 페이지를 랜더링 하는 방법은 총 3가지로 나뉩니다

 

SSG (Static Site Generation) - getStaticProps

 

공식문서에서 대부분의 경우 사용하기를 추천하는 방식으로 빌드타임 때 미리 HTML을 생성해 놓고 요청이 들어왔을 때 해당 페이지를 보여주는 방식입니다. Data Fetching은 빌드타임에 이루어지기 때문에 콘텐츠가 너무 자주 바뀌지 않는 페이지에 적합합니다. revalidate라는 옵션을 주면 매번 새롭게 빌드를 하지 않더라도 주기적으로 새로운 데이터를 불러올 수도 있습니다. 

 

SSR (Server Side Rendering) - getServerSideProps

 

새로운 요청이 들어올 때마다 런타임 때 HTML을 생성하고 보여주는 방식입니다.

콘텐츠가 주기적으로 업데이트되는 소셜미디어 피드 같은 다이내믹한 콘텐츠에 적합합니다.

 

CSR (Client Side Rendering)

 

보통은 위 두 가지 방법으로 랜더링이 이루어지지만 기존 SPA 방식인 CSR도 지원합니다. Pre-rendering을 하지 않고 데이터를 매번 컴포넌트가 마운트 될 때마다 불러오는 방법으로 SEO나 로딩 속도는 현저히 떨어지지만 사용자 입력에 즉각 반응하여 더 좋은 사용자경험을 전달할 수도 있다는 장점이 있습니다.

 


Server Component VS Client Component

Next 13에서 새롭게 변화된 Data Fetching을 사용해 보기 앞서, 13 버전을 사용하기 위해 필요한 React 18 버전에서 어떤 변화가 있었는지 먼저 살펴보도록 하겠습니다.

 

가장 큰 변화중 하나이자 Next JS 13의 Data Fetching에 영향을 준건 서버 컴포넌트와 클라이언트 컴포넌트의 구분이었습니다. React 18 이전 버전에서에서는 클라이언트 컴포넌트로 모든 걸 처리했습니다.

 

하지만 새로운 버전에서는 서버 관련 로직을 내포하고 있는 서버 컴포넌트 (Server Component), 그리고 커스텀 훅, 이벤트 리스너, 브라우저 api 등  클라이언트 (Client) 로직을 사용할 수 있는 컴포넌트로 분리하였습니다. 후자는 이름만 클라이언트 컴포넌트지 기존에 사용하던 hyrdation SSR방식의 컴포넌트입니다.

 

서버 컴포넌트로 인해 이제 개발자들은 컴포넌트에 서버관련 로직을 바로 적용시킬 수 있게 되었기 때문에 클라이언트에서 전송되어야 하는 데이터의 양을 현저히 줄일 수 있게 되었습니다. 물론 클라이언트 로직이 필요한 곳은 그대로 클라이언트 컴포넌트를 사용하면 되고 서버 관련 로직이 필요한 곳에서 만 서버 컴포넌트를 사용해서 더 효과적으로 랜더링 하는 게 가능해진 것입니다.

 

이로써 Next JS에서도 해당 서버 컴포넌트를 활용해 더욱더 간결하게 Data Fetching이 가능해졌습니다.

 

컴포넌트 레벨에서 서버/클라이언트 구분해 사용가능해진 NEXT 13 - Next JS 공식문서

 

 


훨씬 간결해진 Next 13의 Data Fetching

Next 13에서 새롭게 추가된 App 디렉터리 (이 시리즈의 첫 글 참고)에 있는 모든 컴포넌트의 디폴트값은 앞서 설명한 서버 컴포넌트로 세팅되어 있습니다. 그 말은 즉슨, 서버에서 데이터를 불러오는 작업을 별도의 추가적인 세팅 없이 바로 할 수 있다는 겁니다. 

 

비교하기 쉽도록 예전에 Next 13 이전의 Pre Rendering 포스트에서 사용했던 JSON Placeholder에서 포스트를 불러오는 예제를 그대로 사용해 보도록 하겠습니다.

 

SSR

cache: "no-store"
// app/posts/page.js

const fetchPosts = async () => {
  const response = await fetch("https://jsonplaceholder.typicode.com/posts", {
    cache: "no-store",
  });
  return response.json();
};

const Posts = async () => {
  const data = await fetchPosts();
  return data.map((item, index) => <div key={item.id}>{item?.title}</div>);
};

export default Posts;

 

기존의 getServerSideProps 같은 다소 복잡한 이름대신 직관적으로 사용자가 원하는 함수(fetchPosts)를 정의하고 해당 함수로 Post데이터를 불러온 걸 확인할 수 있습니다.

 

💡 캐시 옵션의 "no-store” 옵션은 말 그대로 해당 쿼리에 대해서는 자동으로 캐싱 작업을 하지 않고 , 매번 새로운 요청이 있을 때마다 새롭게 데이터를 불러오도록 설정해 주는 겁니다.

 

SSG 

cache: "force-cache" // 디폴트값으로 생략가능
// app/posts/page.js

const fetchPosts = async () => {
  const response = await fetch("https://jsonplaceholder.typicode.com/posts"); // {cache: "force-cache"} 생략
  return response.json();
};

const Posts = async () => {
  const data = await fetchPosts();
  return data.map((item, index) => <div key={item.id}>{item?.title}</div>);
};

export default Posts;

마찬가지로 기존에는 SSR과 SSG를 적용하기 위해선 아예 다른 메서드 (getStaticProps, getServerSideProps)를 사용하여 데이터를 불러오고 Props로 넘겨줘야 했지만 13 버전 이후에서는 위에 예제처럼 cache 옵션만 변경해 주면 랜더링 방식을 바꿀 수 있습니다.

 

ISR

SSG는 사양하되 주기적으로 데이터를 새롭게 불러오는 ISR (Incremental Static Regeneration) 방식을 사용하려면 어떡해야 할까요?

기존에는 props와 함께 revalidate 옵션을 넘겨주었는데요. 

 

Next JS 13에서도 마찬가지로  revalidate 옵션에 몇 초마다 데이터를 새로 fetch 할 것인지 아래와 같이 게시해 주시면 됩니다.

 

const fetchPosts = async () => {
  const response = await fetch("https://jsonplaceholder.typicode.com/posts", {
    next: {
      revalidate: 10,
    },
  });
  return response.json();
};

 

위와 같이 작성하시면 10초마다 json placeholder api에 posts 글 요청을 하게 됨으로, 그 안에 새로운 글이 발했되었다면 따로 빌드를 하지 않아도 해당 글을 볼 수 있게 됩니다.

 


 

이제 좀 더 나아가 상세 페이지를 한번 만들어보도록 하겠습니다. 

이전 버전에서 동적으로 내용이 변할 수 있는 상세페이지를 빌드타임에 만들기 위해서는 getStaticPaths로 미리 생성해들 글의 path (이 예제에서는 id)를 미리 제시할 수 있었습니다. 

 

예를 들어 100개의 앨범 정보중 10개의 앨범 정보 페이지만 빌드타임에 만들어 두기 위해선 아래와 같이 작성을 하였습니다.

 

Next 12에서 Pre-rendering

// ... [NEXT.js] 넥스트 JS를 배워보자 1편 - Pre Rendering 편 참고
export const getStaticPaths = async () => {
  const res = await fetch(
    "https://jsonplaceholder.typicode.com/photos?_start=0&_end=10"
  );
  const photos = await res.json();
  const ids = photos.map((photo) => photo.id);
  const paths = ids.map((id) => {
    return {
      params: { id: id.toString() },
    };
  });
  return {
    paths,
    fallback: false, // 해당 함수로 return 되지 않는 path에 대한 요청은 404페이지로 응답
  };
};
// ...생략

paths와 fallback 여부로 빌드 타임에 생성되지 않는 path로 요청이 들어오면 어떤 식으로 동작할지(404 에러 페이지, 백그라운드에서 정적 페이지 추가, SSR방식처럼 작동)를 미리 지정해 두는 게 가능했었습니다. Next JS 13 버전에서도 해당 방법이 크게 달라지진 않았습니다.

 

Next 13에서 Pre-rendering

generateStaticParams

아래 예제에서는 Next 13에서 id 0부터 10까지의 앨범 이미지 정보 페이지를 새롭게 추가된 generateStaticParams을 통해 빌드타임 때 미리 만들어두고 있습니다.

// app/photos/[id]/page.js

import Image from "next/image";

const fetchPosts = async (params) => {
  const res = await fetch(
    `https://jsonplaceholder.typicode.com/photos/${params.id}`
  );
  const data = await res.json();
  return data;
};

export async function generateStaticParams() {
  const res = await fetch(
    "https://jsonplaceholder.typicode.com/photos?_start=0&_end=10"
  );
  const photos = await res.json();
  return photos.map((photo) => ({
    id: photo.id.toString(),
  }));
}

// 편의상 style 속성을 사용했습니다
const Page = async ({ params }) => {
  const { title, url } = await fetchPosts(params);
  return (
    <section>
      <h1 style={{ textAlign: "center" }}>{title}</h1>
      <div
        style={{
          position: "relative",
          height: "600px",
          width: "600px",
          margin: "0 auto",
        }}
      >
        <Image fill src={url} alt={title} />
      </div>
    </section>
  );
};

export default Page;

 

 

해당 예제에서는 api에서 불러올 수 있는 최대 앨범 정보가 100개라 빌드 타임 때 그냥 다 만들어 두게 해두어도 무방하지만 추후에 더 많은 정보를 불러와야할때는 많은 시간이 소요되므로 가장 최근 정보나 인기 많은 글들 위주로 빌드 해놓고 나머지는 이전 버전의 fallback 역할을 하는 dynamicParams 옵션을 통해 빌드 타임때 생성되지 않은 페이지를 어떻게 처리할 것인지 명시할 수 있습니다.

 

💡 next dev (개발환경)에서, generateStaticParams은 라우트를 이동할 때 실행됩니다
💡 next build (빌드, 배포 시) , generateStaticParams 은 해당 레이아웃이나 페이지가 만들어지기 전에 실행됩니다.
💡 ISR시 (revalidate) , generateStaticParams 은 다시 실행되지 않습니다

 

이상으로 NEXT 13에서 Data Fetching 하는 방법에 대해 알아보았습니다.

헷갈릴 수도 있음에도 굳이 이전 버전에서의 방식을 언급했던 이유는 Next는 Incremental Adoption을 지향하기 때문입니다. 한 번에 모든 것이 바꾸지 않고 조금씩 필요에 의해 기존 버전의 기능을 유지한 채 천천히 적용시키고 있습니다. 그렇기 때문에 프로젝트를 진행하다 보면 언젠가 기존 방식의 코드들을 보게 될 것이므로 이런것들도 있었구나 하면서 넘어가주시면 될것 같습니다.

 

다른 궁금하신점이 있다면 언제든지 댓글로 알려주시면 답변드리겠습니다.

 

참고 자료:  Next 공식문서, What Do The New Bleeding-Edge Features Actually Do?