<dev.log/>;

Next.js의 data-feching

Pre-rendering

기본적으로 Next.js는 모든 페이지를 pre-render한다. 각각의 페이지가 클라이언트 사이드에서 렌더링 되기전 미리 데이터 페칭을 한다는 뜻이다. Next.js는 정적 생성을 기본적으로 사용하지만 페이지에 데이터 페칭 방법을 선택할 수 있다.

기본적으로 바닐라 리액트가 제공하는 CSR과 Next.js가 지원하는 pre-rendering에 사용하는 방식인 SSR, SSG(+ISR) 들을 알아보자.

CSR

클라이언트 측 데이터 가져오기는 페이지에 SEO 인덱싱이 필요하지 않거나, 데이터를 사전 렌더링할 필요가 없거나, 페이지 콘텐츠를 자주 업데이트해야 하는 경우에 유용하다.

하지만 클라이언트에서 데이터를 가져올시 애플리케이션의 성능과 페이지의 로드 속도에 영향을 미칠 수 있다. 이는 페이지가 마운트될 때 데이터를 가져오고 그 데이터가 캐시되지 않기 때문이다.

useEffect를 사용하여 가져오기

function Profile() {
  const [data, setData] = useState(null);
  const [isLoading, setLoading] = useState(false);

  useEffect(() => {
    setLoading(true);
    fetch('/api/profile-data')
      .then((res) => res.json())
      .then((data) => {
        setData(data);
        setLoading(false);
      });
  }, []);

  if (isLoading) return <p>Loading...</p>;
  if (!data) return <p>No profile data</p>;

  return (
    <div>
      <h1>{data.name}</h1>
      <p>{data.bio}</p>
    </div>
  );
}

SSR

페이지 파일에서 getServerSideProps 라는 함수를 내보내면 Next.js는 반환된 데이터를 사용하여 각 요청에서 페이지를 미리 렌더링한다.

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의 특징

  • 서버 측에서만 실행되고 브라우저에서는 실행되지 않는다.

  • 오로지 페이지 파일에서만 내보낼 수 있다.

  • 캐시 제어 헤더가 구성된 경우에만 캐시가 가능하다.

    export async function getServerSideProps({ req, res }) {
      res.setHeader('Cache-Control', 'public, s-maxage=10, stale-while-revalidate=59');
    
      return {
        props: {},
      };
    }
    

SSG

정적 생성된 페이지로 getStaticProps 라는 함수를 사용한다.

function Blog({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li>{post.title}</li>
      ))}
    </ul>
  );
}

// This function gets called at build time on server-side.
// It won't be called on client-side, so you can even do
// direct database queries.
export async function getStaticProps() {
  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의 특징

  • 페이지를 렌더링하는 데 필요한 데이터는 사용자 요청보다 앞서 빌드 시간에 사용할 수 있다.
  • 데이터는 공개적으로 캐시된다.(사용자별이 아니다.)
  • 항상 서버에서 실행되고 클라이언트에서는 실행되지 않는다.
  • 개발 환경에서는 모든 요청에서 호출되므로 build 이후에 제대로 작동하는지 테스트 해본다.

getStaticPath란?

페이지에 동적 경로가 있고 getStaticProps를 사용하는 경우에는 정적으로 생성할 경로 목록을 정의해야 한다.

// pages/posts/[id].js

// Generates `/posts/1` and `/posts/2`
export async function getStaticPaths() {
  return {
    paths: [{ params: { id: '1' } }, { params: { id: '2' } }],
    fallback: false, // can also be true or 'blocking'
  };
}

// `getStaticPaths` requires using `getStaticProps`
export async function getStaticProps(context) {
  return {
    // Passed to the page component as props
    props: { post: {} },
  };
}

export default function Post({ post }) {
  // Render post...
}
  • getStaticProps와 함께 사용해야 한다.

ISR

ISR을 사용하면 전체 사이트를 재구축할 필요 없이 페이지별로 정적 생성을 사용할 수 있다. ISR을 사용하면 정적의 이점을 유지하면서 수백만 페이지로 확장할 수 있다.

ISR을 사용하기 위해서는 getStaticProps의 설정에 revalidate 설정을 추가한다.

function Blog({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

// This function gets called at build time on server-side.
// It may be called again, on a serverless function, if
// revalidation is enabled and a new request comes in
export async function getStaticProps() {
  const res = await fetch('https://.../posts');
  const posts = await res.json();

  return {
    props: {
      posts,
    },
    // Next.js will attempt to re-generate the page:
    // - When a request comes in
    // - At most once every 10 seconds
    revalidate: 10, // In seconds
  };
}

// This function gets called at build time on server-side.
// It may be called again, on a serverless function, if
// the path has not been generated.
export async function getStaticPaths() {
  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: blocking } will server-render pages
  // on-demand if the path doesn't exist.
  return { paths, fallback: 'blocking' };
}

export default Blog;