코딩/React

NextJS Data 가져오는 방법 (SSG, SSR, ISR)

드리프트 2021. 9. 12. 21:07
728x170

목차

 

1. Static Site Generation (SSG) 란?

 

2. Server Side Rendering (SSR) 란?

 

3. 스켈레톤 앱 만들기

 

4. Client Side Rendering (CSR) 코드

 

     4-1. CSR의 특징

 

5. Server Side Rendering (SSR) 코드

 

    5-1. SSR의 특징

 

6. CSR과 SSR의 차이점

 

7. Static Site Generation (SSG) 코드

 

    7-1. SSG의 특징

 

8. Incremental Static Regeneration (ISR) 코드

 

   8-1. ISR의 특징

 

 

 

 

 

안녕하세요?

 

오늘은 NextJS의 주요 기능 중 하나인 Data Fetching에 대해 알아보겠습니다.

 

먼저, 오늘 우리가 알아볼 Data Fetching 종류에 대해 살펴보겠습니다.

 

 

1. Static Site Generation (SSG) 란?

 

정적 사이트 생성이라고 번역할 수 있는데요.

 

보통 React앱은 Client Side Rendering (CSR)이라고 해서 껍데기 HTML 파일에서 자바스크립트로 브라우저 상에서 DOM을 확장시켜 나가는 방식입니다.

 

그래서 보통 React앱은 초기 로딩 속도가 느리고 브라우저가 램을 많이 잡아먹게 되죠.

 

클라이언트 쪽에서 렌더링이 일어나는 것보다는 그냥 일반적인 HTML을 로딩하는 게 브라우저 입장에선 훨씬 빠릅니다.

 

그래서 나온 것이 NextJS입니다.

 

Gatsby 가 정적 사이트 블로그 만들 때 아주 유명한데요.

 

이 Gatsby도 NextJS로 만들어졌습니다.

 

 

 

2. Server Side Rendering (SSR) 란?

 

서버사이드 렌더링이라고 보통 유저가 브라우저를 통해 사이트에 접속하면 사이트 서버에서 렌더링에 필요한 데이터를 유저 쪽 즉, 클라이언트 쪽에 보내주는 방식입니다.

 

이 방식은 Client Side Rendering 보다 첫 접속 시 훨씬 빠른 전송속도를 보여주기 때문에 사용자들이 느끼는 속도감이 훨씬 좋습니다.

 

그리고 모든 데이터가 일반적인 HTML에 포함돼 있기 때문에 구글 같은 써치 엔진이 크롤링하기에 매우 유리합니다.

 

이쪽 업계에서는 SEO가 좋다고 하는데요. Search Engine Optimization이라고 합니다.

 

써치 엔진에 최적화되어 있다는 뜻입니다.

 

써치 엔진에 최적화되어야 본인의 사이트가 유저들이 쉽게 찾아 들어올 수 있기 때문에 현대 웹 프런트엔드 쪽에서는 SEO를 가장 중요한 지표로 사용하고 있습니다.

 

본인도 첫 페이지 로딩이 오래 걸리면 바로 뒤로 가기 해버리는 경향이 있거든요.

 

모든 사용자들이 홈페이지 로딩을 못 견뎌한다고 합니다.

 

그래서 Server Side Rendering이 그렇게 중요한 이유가 있는 겁니다.

 

그러나 Server Side Rendering의 약점은 로딩한 페이지에서 동적으로 서버에서 데이터를 가져오지 못하는 치명적인 약점이 있습니다.

 

사용자가 많고, 데이터 정보가 클수록 서버 부담이 과중되는데요. 이때 발생하는 게 랙입니다.

 

일단 오늘 우리가 배울 NextJS의 Data Fetching에 있어 가장 중요한 용어는 배웠으니까 본론으로 들어가 보겠습니다.

 

 

 

3. 스켈레톤 앱 만들기

 

일단 테스트 앱을 만들도록 하겠습니다.

 

npx create-next-app understanding_nextjs_data_fetching

Create-React-App처럼 NextJS에서 Create-Next-App이 있습니다.

 

이제 초기 앱 템플릿을 만들었으니까 이 앱에 들어가서 필요 없는 거는 삭제하도록 하겠습니다.

 

pages/index.js

import Head from "next/head";

export default function Home() {
  return (
    <div>
      <Head>
        <title>Create Next App</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <p>NextJS</p>
    </div>
  );
}

 

본격적인 NextJS Data Fetching에 들어가기 전에 일반적인 React앱에서 Data Fetching 하는 방법에 대해 알아보겠습니다.

 

가장 일반적으로 CSR(Client Side Rendering)에서는 페이지가 처음 뜰 때 데이터를 가져오는 방법은 React Hook 인 useEffect를 사용합니다.

 

그럼, 먼저 useEffect를 사용해서 CSR(Client Side Rendering)하는 방법에 대해 알아보겠습니다.

 

우리가 만들려고 하는 앱은 Worldtime API에서 시간을 가져와서 페이지에 보여주는 앱인데요.

 

'worldtimeapi.org/api/ip'로 fetch 하면 현재 시간을 JSON 형태로 리턴해줍니다.

 

크롬에서는 JSON 파일이 RAW 파일로 보이고요, 밑에 파이어폭스에서는 JSON 파일을 보기 좋게 형식화했네요.

{"abbreviation":"KST","client_ip":"59.7.152.40","datetime":"2021-09-12T19:02:22.254943+09:00","day_of_week":0,"day_of_year":255,"dst":false,"dst_from":null,"dst_offset":0,"dst_until":null,"raw_offset":32400,"timezone":"Asia/Seoul","unixtime":1631440942,"utc_datetime":"2021-09-12T10:02:22.254943+00:00","utc_offset":"+09:00","week_number":36}

일단 우리가 Fetch 해야 할 Data를 확인했으니까, 본격적으로 CSR(Client Side Rendering)에 대해 살펴보겠습니다.

 

일단 시간을 좀 더 보기 좋게 보여주는 패키지인 date-fns를 깔겠습니다.

 

또, fetch 패키지인 axios도 설치하겠습니다.

 

npm i date-fns axios

 

그럼, CSR의 useEffect 코드를 알아보겠습니다.

 

 

4. Client Side Rendering (CSR) 코드

 

 

pages/index.js

import React from "react";
import axios from "axios";

import TimeSection from "../components/timesection";

export default function Home() {
  const [dateTime, setDateTime] = React.useState("");

  React.useEffect(() => {
    axios
      .get("https://worldtimeapi.org/api/ip")
      .then((res) => {
        setDateTime(res.data.datetime);
        return res.data.datetime;
      })
      // eslint-disable-next-line no-console
      .catch((error) => console.error(error));
  }, []);

  return (
    <div
      style={{
        textAlign: "center",
      }}
    >
      <h1>CSR Time</h1>
      <main>
        <TimeSection
          title="CSR"
          description="렌더될때마다 클라이언트 사이드에서 fetch 됩니다."
          dateTime={dateTime}
        />
      </main>
    </div>
  );
}

 

일단 코드가 어려워 보이는데요.

 

천천히 하나하나 살펴보겠습니다.

 

React와 axios를 import 하고 다음에 나올 TimeSection 부분도 import 합니다.

 

그리고 메인 컴포넌트에서 useState로 dateTime을 설정하고

 

useEffect 훅으로 "worldtimeapi.org/api/ip"에서 시간 데이터를 fetch 해옵니다.

 

useEffect의 두 번째 파라미터에 빈 배열을 넣어주면 처음 컴포넌트가 로드될 때 한 번만 실행되게 됩니다.

 

그리고 useEffect 훅에서 axios로 데이터 fetch 된 data를 우리의 State인 dateTime에 적용시키게 됩니다.

 

이제 우리는 dateTime state를 가지고 렌더링 하면 됩니다.

 

코드를 계속 보시면 dateTime을 렌더링 하는 방법은 바로 TimeSection 컴포넌트에 전달해서 보여주고 있습니다.

 

TimeSection 컴포넌트를 살펴보겠습니다.

 

components/timesection.js

import React from "react";
import { format } from "date-fns";

import useRealTime from "../hooks/useRealTime";

export default function TimeSection({ dateTime, title, description }) {
  const cleanDate = dateTime && format(new Date(dateTime), "kk:mm:ss O");

  const realTime = useRealTime();

  return (
    <section>
      <div>
        <h1>{title}</h1>
        <p>{description}</p>
        <h2>{cleanDate ? cleanDate : "LOADING..."}</h2>
      </div>

      <div>
        <p>Real Time:</p>
        <p>{realTime}</p>
      </div>
    </section>
  );
}

 

코드를 보시면 date-fns 패키지에서 format 패키지를 import 하고 있으며, 그리고 실제 시간을 가져오는 커스텀 훅인 useRealTime을 import 해오고 있습니다.

 

useRealTime 훅은 밑에서 바로 알아볼 예정이니 여기서는 TimeSection 컴포넌트에 대해 먼저 알아보겠습니다.

 

TimeSection 컴포넌트는 props로 dateTime, title, description을 받아오는데 이름만 봐도 쉽게 유추할 수 있습니다.

 

첫 번째 코드를 보시면 cleanDate를 설정합니다.

 

그리고 cleanDate가 undefined 됐을 때, 다시 말해 dateTime 아직 없을 경우, 즉 axios가 data를 fetching 하는 시간에 "LOADING..."이라는 문구를 로딩하는 코드가 있습니다.

 

그리고 마지막에 RealTime 커스텀 훅에서 실제 시간을 가져와서 보여주고 있습니다.

 

 

hooks/useRealTimes.js

import React from "react";
import { format } from "date-fns";

export default function useRealTime() {
  const [realTime, setRealTime] = React.useState(
    format(new Date(), "kk:mm:ss O")
  );

  React.useEffect(() => {
    const intervalId = setInterval(() => {
      setRealTime(format(new Date(), "kk:mm:ss O"));
    }, 1000);

    return () => {
      clearInterval(intervalId);
    };
  }, []);

  return realTime;
}

 

마지막으로 useRealTime 커스텀 훅입니다.

 

이 훅은 현재 시간을 1초마다 갱신해서 보여 주는 코드입니다.

 

자 이제, 모든 코드가 완성되었으니까 실행 화면을 보도록 하겠습니다.

 

 

보시면 마지막에 있는 Real Time은 1초마다 계속 갱신되고 있습니다.

 

그런데 가운데 큰 글씨로 있는 시간은 처음 로딩될 때 그 시간에서 멈춰 있습니다.

 

이게 바로 Client Side Rendering의 useEffect 특성입니다.

 

즉, CSR은 페이지가 처음 브라우저에서 로딩될 때 useEffect 훅에 의해 시간 Data를 Fetching 해서 가져오고 있으며,

 

여기서 중요하게 봐야 할 특징은 바로 한 번만 가져온다는 겁니다.

 

페이지를 새로 고침 하면 잠깐 LOADING... 가 보이더니 금세 새로운 시간을 가져오게 됩니다.

 

중요한 요점을 정리하자면

 

 

4-1. CSR의 특징

 

1. useEffect 함수 : Client Side Rendering 방식을 이용할 때 사용하는 함수

 

2. LOADING... 문구 : 이 문구가 나타나는 이유는 페이지가 처음 로딩될 때 아직 데이터가 fetch 되기 전이라는 짧은 시간이 존재한다는 걸 보여줍니다.

 

3. 새로고침 할 때마다 시간이 바뀌는 걸로 보아 새로 고침은 매번 Data Fetch를 유발합니다.

 

 

 

 

5. Server Side Rendering (SSR) 코드

 

이제 서버사이드 렌더링일 때의 모습을 살펴보겠습니다.

 

pages 폴더에 ssr.js 란 파일을 따로 만들겠습니다.

 

pages/ssr.js

import React from "react";
import axios from "axios";

import TimeSection from "../components/timesection";

export default function SSRPage({ dateTime }) {
  return (
    <div
      style={{
        textAlign: "center",
      }}
    >
      <h1>SSR Time</h1>
      <main>
        <TimeSection title="SSR" dateTime={dateTime} />
      </main>
    </div>
  );
}

export const getServerSideProps = async () => {
  const res = await axios.get("https://worldtimeapi.org/api/ip");

  return {
    props: { dateTime: res.data.datetime },
  };
};

코드는 일단 CSR 모습과 비슷한데요.

 

마지막에 있는 코드가 뭔가 이상합니다.

 

getServerSideProps 이란 함수가 있는데요.

 

이게 바로 NextJS가 유명한 이유입니다.

 

getServerSideProps 함수를 async 즉 비동기로 실행시키면 pages/ssr.js 파일이 로딩될 때 서버 쪽 명령어를 실행시켜 데이터를 가져올 수 있습니다.

 

axios로 data fetch 후 props 객체를 리턴하면 우리의 컴포넌트에서 그 props를 이용할 수 있게 됩니다.

 

즉, getServerSideProps 함수만 있으면 Express를 이용한 Node Server가 필요 없게 되는 거죠.

 

이런 걸 Serverless 방식이라고 하는데, AWS, Google Firebase, Azure 등 요즘 웹 호스팅 업체에서 많이 제공하고 있습니다.

 

그래서 pages/ssr.js를 브라우저에서 로딩해 보겠습니다.

 

 

뭔가 Client Side Rendering의 useEffect 훅을 이용한 것과 비슷하게 반응하는데요.

 

맞습니다.

 

서버사이드 렌더링도 페이지가 처음 로딩될 때 한 번만 Data가 fetch 됩니다.

 

여기서 중요한 점을 요약해 보면

 

 

5-1. SSR의 특징

 

1. getServerSideProps 함수로 NextJS에서 서버사이드 렌더링을 할 수 있습니다.

 

2. CSR 방식일 때 있던 LOADING... 문구가 안 보입니다. 그리고 렌더 시 살짝 딜레이가 있습니다. 왜냐하면 서버사이드 렌더링은 페이지가 렌더 되기 전에 getServerSideProps 함수를 이용해서 Data를 먼저 Fetch 하게 되고, Data가 Fetch 되면 렌더링 되기 때문에 LOADING... 문구가 보일 수 없게 됩니다.

 

3. CSR처럼 SSR도 새로고침 할 때마다 시간이 바뀌는 걸로 보아 새로 고침은 매번 Data Fetch를 유발합니다.

 

 

 

6. CSR과 SSR의 차이점

 

1. CSR의 경우 페이지 렌더링 할 때 딜레이는 없지만 LOADING... 문구가 보이며,

   => CSR은 페이지가 로드된 후에 API에 접근

 

2. SSR의 경우 약간의 딜레이는 있지만 LOADING... 문구 없이 바로 페이지가 렌더링 됩니다.

  => SSR은 페이지 로드 전에 API에 접근

 

 

CSR의 경우 페이지가 로드된 후 데이터가 fetch 되기 때문에 SEO에 부적합하지만 유저 로그인 같은 세션 구현에는 좋은 방법입니다.

 

그래서, 사용자별 대시보드나 사용자 프로파일 에디트 화면 같은 경우 CSR 방식을 많이 이용합니다.

 

반면, SSR의 경우에는 SEO에 좋기 때문에 트래픽을 불러올 필요가 있는 블로그에 적합한 방식입니다.

 

 

 

7. Static Site Generation (SSG) 코드

 

이제, NextJS의 중요한 특징 중 하나인 정적 사이트 생성에 대해 알아보겠습니다.

 

 

pages/ssg.js

import React from "react";
import axios from "axios";

import TimeSection from "../components/timesection";

export default function SSGPage({ dateTime }) {
  return (
    <div
      style={{
        textAlign: "center",
      }}
    >
      <h1>SSG Time</h1>
      <main>
        <TimeSection title="SSG" dateTime={dateTime} />
      </main>
    </div>
  );
}

export const getStaticProps = async () => {
  const res = await axios.get("https://worldtimeapi.org/api/ip");

  return {
    props: { dateTime: res.data.datetime },
  };
};

 

SSG (Static Site Generation)의 경우입니다.

 

getStaticProps 빼고는 달라진 코드가 없습니다.

 

그렇습니다. 바로 이 함수가 정적 사이트 생성할 때 필요한 NextJS의 함수입니다.

 

NextJS 공식 문서를 보면 getStaticProps 함수는 next build 명령어가 실행될 때 getStaticProps 함수가 실행한다고 합니다.

 

즉, 사이트가 빌드될 때 한번 서버사이드 Data Fetch를 하게 되고 이때 얻은 데이터를 이용해서 페이지를 렌더링 한다는 뜻입니다.

 

next build를 다시 하기 전에는 처음 한 번 실행했을 때의 Data로 계속 페이지가 보이게 되는 겁니다.

 

SSG 방식도 앞에 경우와 비슷합니다.

 

SSG의 특징을 살펴보자면,

 

 

7-1. SSG의 특징

 

1. getStaticProps 함수로 정적 사이트를 만들며,

 

2. next build 명령어로 전체 앱을 빌드할 때 한 번만 실행됩니다.

 

3. 그래서 더 이상 Data 가 변하지 않고 처음 보이는 Data 그대로 유지됩니다.

 

 

 

 

8. Incremental Static Regeneration (ISR) 코드

 

점진적 정적 재생성이라고 변역해야 될까요?

 

ISR이 그렇습니다.

 

이 방식은 SSG(Static Site Generation)의 단점을 보완한 겁니다.

 

일단 코드를 보면서 설명하겠습니다.

 

pages/isr.js

import React from "react";
import axios from "axios";

import TimeSection from "../components/timesection";

export default function ISRPage({ dateTime }) {
  return (
    <div
      style={{
        textAlign: "center",
      }}
    >
      <h1>SSG Time</h1>
      <main>
        <TimeSection title="ISR" dateTime={dateTime} />
      </main>
    </div>
  );
}

export const getStaticProps = async () => {
  const res = await axios.get("https://worldtimeapi.org/api/ip");

  return {
    props: { dateTime: res.data.datetime },
    revalidate: 20,
  };
};

코드를 보시면 SSG랑 비슷한데 마지막에 리턴되는 객체에서 revalidate 항목이 있습니다.

 

revalidate란 몇 초마다 getStaticProps를 실행하는지 알려주는 항목인데요.

 

우리는 20초로 정했습니다.

 

실행 화면을 볼까요?

 

 

첫 번째 로딩 후 찍은 화면입니다.

 

새로 고침 몇 번 했습니다.

 

SSG(Static Site Generation)과 다른 점이 보이시나요?

 

SSG 방식은 next build 한번 하면 더 이상 바뀌지 않는데 ISR 방식은 20초 보다 더 길게 페이지 새로고침 하면 시간이 바뀌는 걸 볼 수 있습니다.

 

즉, ISR은 revalidate 시간만큼 매번 계속 실행할 수 있는 getStaticProps 함수를 가질 수 있습니다.

 

ISR의 중요한 특징을 살펴보면

 

 

8-1. ISR의 특징

 

1. 페이지를 처음 방문하면 데이터는 SSG 방식과 마찬가지로 Build 시 처음 가져온 데이터입니다.

 

2. 그러나, ISR은 revalidate 시간이 지나게 되면 이 페이지만 서버사이드에서 재 빌드하게 되는데요, API를 백그라운드에서 접속하기 때문에 클라이언트 쪽에 보이는 데이터는 변함이 없습니다.

 

3. revalidate 시간 후에 새로고침을 누르면 ISR이 페이지를 재 빌딩 하는 시간 후에 렌더링 되기 때문에 실제 시간보다 1초 늦게 보입니다. 여기서 1초라는 뜻은 페이지 재 빌딩이 1초 걸린다는 뜻입니다.

 

 

ISR은 처음 빌드된 후 더 이상 이 페이지에 재방문이 없으면, revalidate 시간이 경과했더라도 백그라운드에서 재 빌드되지 않습니다.

 

그리고 만약 revalidate 시간 경과 후 재방문했을 경우에는 처음 접속 때 re-building이 실행되고 한번 더 페이지 새로고침을 하면 새로운 Data로 페이지가 렌더링 하게 됩니다.

 

만약 이 페이지에 여러 사람이 접속한다면 이론처럼 revalidate 시간을 기준으로 재 빌드가 이루어지게 되는 거죠.

 

본인이 방문 안 해도 다른 사람이 방문해서 재 빌드가 이루어진걸 본인이 보게 되는 겁니다.

 

 

 

 

지금까지 NextJS의 Data Fetching 방법에 대해 알아보았는데요.

 

각각의 경우 다 그 나름대로의 장점이 있기 때문에 경우에 맞게 사용하시면 됩니다.

 

그럼.

 

그리드형