NextJs의 Data Fetching

김유진·2023년 4월 22일
0

Nextjs

목록 보기
2/9
post-thumbnail

이번 글에서는 NextJs에서 데이터를 패칭하는 방법을 크롬 개발자 도구를 하나하나 보면서 자세히 정리해보고자 한다. 순서는 NextJs 공식문서대로 따라가면서 정리할 예정이다.

getStaticProps

getStaticProps를 사용하게 되면, Next.js는 getStaticProps를 사용하여 리턴된 props를 이용하여 페이지를 빌드 타임에 pre-render한다.

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

언제 getStaticProps를 이용해야 할까?

  • 유저의 요청 전에 페이지를 빌드 타임에 미리 랜더해도 되는 페이지
  • getStaticProps 함수는 HTML과 JSON파일을 만들어 서버로부터 매우 빠르게 정보를 받아올 수 있다.

Server-side code를 직접적으로 작성할 수 있다.

getStaticProps는 서버 사이드에서만 작동하고, 클라이언트 사이드에서는 작동하지 않는다. 그래서 외부에서 데이터를 패칭해오는 API route 코드를 작성할 때에는 바로 getStaticprops를 이용하여 서버 사이드에서 api를 처리하는 코드를 작성하면 된다.

// lib/load-posts.js

// The following function is shared
// with getStaticProps and API routes
// from a `lib/` directory
export async function loadPosts() {
  // Call an external API endpoint to get posts
  const res = await fetch('https://.../posts/')
  const data = await res.json()

  return data
}

// pages/blog.js
import { loadPosts } from '../lib/load-posts'

// This function runs only on the server side
export async function getStaticProps() {
  // Instead of fetching your `/api` route you can call the same
  // function directly in `getStaticProps`
  const posts = await loadPosts()

  // Props returned will be passed to the page component
  return { props: { posts } }
}

getStaticProps 함수에서 api를 불러오는 함수를 작성함으로써 서버 사이드쪽의 API 호출을 완성시킬 수 있다.
그럼 getStaticProps 함수가 어떻게 작동하는지 코드를 직접 하나하나 작성해보면서 알아보도록 하자.

사용 예시

아래와 같이 코드를 작성하고 접속해보자.

import type { NextPage } from 'next';

interface Props {
  data: number;
}

const Example: NextPage<Props> = ({ data }) => {
  return (
    <main>
      <h1>getStaticProps Page</h1>
      <p>: {data}</p>
    </main>
  );
};

export default Example;

export async function getStaticProps() {
  const delayInSeconds = 2;
  const data = await new Promise((resolve) =>
    setTimeout(() => resolve(Math.random()), delayInSeconds * 1000)
  );

  return {
    props: { data },
  };
}

2초마다 데이터를 받아서 그 결과를 화면에 뿌려주는 결과이다.
기대에는 pre-rendering이 실행되기 때문에 html값이 이미 고정되어 나타나야 하는 것처럼 보이나, dev환경(개발환경)에서는 새로고침 될 때마다 getStaticProps 함수가 다시 실행된다. 이것은 구현하려고 했던 SSG 형식이 아니다. SSG 형식으로 개발하기 위해서는 정적으로 고정되어야 하기 때문ㅇ이다. 새로고침을 하면 pending 상태를 지나서 2초 후에 javascript를 불러온다.

이제 빌드를 수행해서 해당 페이지를 SSG 형식으로 만들어보자.

짜잔! 빌드를 수행하니까 새롭게 만든 파일이 먼저 랜더링된다. 알고 있는것과 마찬가지로 2초라는 시간이 걸렸다.ㅎㅎ

이제 새로고침을 해도 2ms라는 시간밖에 걸리지 않는다. 매우 빠르게 데이터를 받아와 랜더링을 수행한다. 성능이 좋을 수밖에!

그리고 x-nestjs-cache 부분을 보면 HIT 으로, 현재 받아온 데이터가 캐싱되고 있는 것을 알 수 있다.

주의할 점

getStaticProps는 page에서만 사용될 수 있다. _app, _document, _error에서 사용할 수 없다.
그 이유는 React는 페이지가 렌더되기 전에 필요한 데이터가 있다. 그 친구들은 필요한 데이터가 충분치 못하기 때문이다.

자..이렇게 getStaticProps를 이용하여 빌드 타임에 API를 한번만 불러오고 끝나게 되면 안될 것이다!!
빌드가 이루어지고 나서도 API가 수정되어 데이터의 값이 바뀌게 될 수도 있다.
그럴 때를 대비하여 만들어진 것이 바로 revaildate를 이용하는 것이다.
이를 통하여 빌드된 정적인 페이지를 주기적으로 업데이트 해줄 수 있다.

ISR 이용하기 (Incremental Static Regenration)

export async function getStaticProps() {
  const delayInSeconds = 2;
  const data = await new Promise((resolve) =>
    setTimeout(() => resolve(Math.random()), delayInSeconds * 1000)
  );

  return {
    props: { data },
    revalidate: 5,
  };
}

페이지 전체를 빌드하지 않고도 새롭게 바뀌는 데이터를 반영할 수 있다.

  • 처음 요청으로부터 5초 동안 데이터가 캐시된 상태로 존재하여 보여진다.
  • 5초 이후, 다음 리퀘스트가 stale 상태로 페이지에 적용될 것이다.
  • Next.js 트리거가 페이지를 background에서 만든다.
  • 페이지가 성공적으로 만들어지게 되면, Next.js는 캐시가 유효한지 아닌지 판단한다. 이후, 새롭게 업데이트 된 페이지를 보여주게 된다. 그럼 이전의 정보는 대체된다.

해당 순서로 진행되며 진행 과정을 자세한 예시로 살펴보자.

5초 동안에 캐시의 상태는 HIT이다. 5초가 지나게 된다면 STALE 상태로 바뀌게 되고

이후 프리랜더링이 실행되어 5초 이후 새롭게 바뀐 값으로 업데이트되고, 당연히 ETag 또한 바뀌게 된다.

Link를 이용하여 라우팅 처리 하기

방금 만들었던 getStaticProps 관련한 페이지로 이동할 수 있는 라우팅 페이지를 만들어 보자.

import Link from 'next/link';
export default function Links() {
  return (
    <main>
      <h1>Links</h1>
      <Link href="/section1/getStaticProps">/getStaticProps</Link>
    </main>
  );
}

코드 작성은 위와 같다.

짜잔! getStaticProps 페이지로 이동을 하였지만 html 파일은 새로 로드되지 않았고, getStaticProps 페이지와 관련된 js파일과 json 파일만 새로 로드된 것을 알 수 있다. 이는 CSR의 랜더링 방식과 매우 유사하여 라우팅과 관련하여 매우 부드럽고 빠르게 수행되고 있는 것을 알 수 있다.
그리고 getStaticProps 부분이 실행되기도 전에 미리 json 파일과 Js파일이 로드되어 있다. 만약 통상적으로 사용하는 a 태그로 해당 부분을 바꾸어 실행한다면 json과 Js파일은 미리 로드되어 있지 않으며, html파일이 새로 로딩된다.

이렇게 이동할 페이지에 대한 정보를 json에 미리 저장해두고, 보이게 될 페이지를 자바스크립트 코드로 미리 설정해두므로 CSR로 빠르게 랜더링할 수 있는 것이다.

또한 더욱 좋은 점은, 화면에 해당 link가 보일 때에만 json파일과 js파일을 로드한다는 것이다. 불필요한 네트워크 요청을 지양하여 더욱 좋은 성능으로 서비스를 운영할 수 있도록 한다.

Link를 이용하지 않는다면?

useRouter를 이용하자!

import { useRouter } from 'next/router';
import { useEffect } from 'react';

export default function Links() {
  const router = useRouter();
  useEffect(() => {
    router.prefetch('/section1/getStaticProps');
  }, [router]);

  return (
    <main>
      <h1>Links</h1>
      <button
        onClick={() => {
          router.push('/section1/getStaticProps');
        }}
      >
        /getStaticProps
      </button>
    </main>
  );
}

이렇게 custom Hook을 이용하여 라우팅 처리를 할 수 있다. onClick 버튼을 만들어 해당 링크로 이동할 수 있게끔 설정해둔다.
하지만 prefetch를 이용하기 위해서는 useEffect를 이용하여 prefetch를 수동으로 이용해야 한다.

getServerSideProps

getServerSideProps를 이용하면 리퀘스트가 들어올 때마다 데이터 랜더링한다.

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

getServerSideProps는 오직 서버쪽에서 동작하고, 브라우저에서는 동작하지 않는다. 그렇기 때문에 pre-rendering을 리퀘스트가 들어올때마다 수행한다.
해당 페이지를 랜더링하기 위한 JSON을 자동적으로 리턴한다. 그리고 해당 함수도 페이지에 해당하는 컴포넌트에서만 사용할 수 있다.

주의해야 할 점은 getStaticprops 와 다르게 빌드타임에 pre-rendering을 수행하는 것이 아니라 request 때마다 pre-rendering을 수행한다는 것이다.

사용 예시

import type { GetServerSideProps, NextPage } from 'next';

interface Props {
  data: number;
}

const Example: NextPage<Props> = ({ data }) => {
  return (
    <main>
      <h1>getServerSideProps Page</h1>
      <p>: {data}</p>
    </main>
  );
};

export default Example;

export const getServerSideProps: GetServerSideProps = async ({ res }) => {
  const delayInSeconds = 2;
  const data = await new Promise((resolve) =>
    setTimeout(() => resolve(Math.random()), delayInSeconds * 1000)
  );

  return {
    props: { data },
  };
};

SSR에도 revaildate를 사용할 수 있지만, 잘 사용하지 않으며 권장하지 않는다.
만약 그에 대한 내용이 궁금하다면 아래 내용을 참고하도록 하자.

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

  return {
    props: {},
  }
}

https://web.dev/i18n/ko/stale-while-revalidate/

CSR (Client Side Rendering)

CSR을 하는 방법은 React에서 코드를 작성했던 방법과 유사하다.
CSR은 페이지에 SEO를 크게 최적화할 필요가 없거나, 데이터를 미리 렌더링할 필요가 없거나, 페이지 내용을 자주 업데이트해야 할 때 유용하다.

import type { NextPage } from 'next';
import { useEffect, useState } from 'react';

const Example: NextPage = () => {
  const [data, setData] = useState(0);

  useEffect(() => {
    const delayInSeconds = 2;
    new Promise<number>((resolve) =>
      setTimeout(() => resolve(Math.random()), delayInSeconds * 1000)
    ).then((result) => setData(result));
  }, []);

  return (
    <main>
      <h1>Client-side data fetching</h1>
      <p>: {data}</p>
    </main>
  );
};

export default Example;

업로드중..

짜잔~ 확실히 SSR과 다른 점이 존재한다. HTML의 초기 상태인 0으로 페이지를 pre-rendering한다. 이후 자바스크립트를 이용하여 상태를 업데이트하여 DOM이 바뀌게 된다.

브라우저의 객체를 자연스럽게 이용해볼까?

만약 내가 작성하고 싶은 컴포넌트에 브라우저 전역 객체인 window 와 같은 것을 이용하여 코드를 작성하고 싶다면 어떻게 해야 할까?
예시를 들어보자!

const NoSSR = () => {
    return <p>width: {window.innerWidth}</p>;
  };
  export default NoSSR;

components/NoSSR 이라는 파일을 하나 만들어서 CSR로 랜더링한다고 해 보자.
업로드중..

이렇게 window가 정의되지 않았다는 경고 메시지가 뜬다. 이 에러가 뜨는 것은 당연하다. 서버에서 어떻게 window를 알아챌 수 있을까! ㅠㅠ 서버에서 랜더링이 되어야 하는 컴포넌트이기 때문에 통하지 않는 것이다.

이렇게 특정 컴포넌트에 SSR을 적용하고 싶지 않을 때에는 next/dynamic 을 이용한다.
코드를 아래와 같이 바꾸어보자.

import type { NextPage } from 'next';
import { useEffect, useState } from 'react';
import dynamic from 'next/dynamic';

const NoSSR = dynamic(() => import('../../components/section1/NoSSR'), {
  ssr: false,
});

const Example: NextPage = () => {
  const [data, setData] = useState(0);

  useEffect(() => {
    const delayInSeconds = 2;
    new Promise<number>((resolve) =>
      setTimeout(() => resolve(Math.random()), delayInSeconds * 1000)
    ).then((result) => setData(result));
  }, []);

  return (
    <main>
      <h1>Client-side data fetching</h1>
      <p>: {data}</p>

      <h1>no SSR</h1>
      <NoSSR />
    </main>
  );
};

export default Example;

dynamic을 이용하여 ssr 옵션을 false로 바꿔주는 것이다.

이렇게 해서 기본적인 데이터 패칭과 관련하여 NextJs의 기본 문법을 정리해보았다! 어려웠지만 서버사이드 랜더링에 익숙해지는 과정 중에 하나라고 생각한다 ㅎㅎ

0개의 댓글