본문 바로가기

React/NextJS

CSR? SSR? SSG? ISR ? NextJS 에서 구현해 보자

SSG [ Static Stie Generation ]

빌드시에 HTML 에 데이터를 담아서 미리 파일을 만들고 유저에게 보여줍니다.

 

장점 :

이미 만들어진 페이지를 보여주는 형식이여서 서버 부담이 적고 응답 속도가 빠릅니다.

 

활용 :

보통 마케팅 페이지나 블로그 글 등 변화가 거의 없는 사이트에 적합합니다.

 

단점 :

데이터가 바뀐다면 빌드와 배포 과정을 다시 해야합니다.

DB에서 데이터가 바뀐다해도 다시 빌드과정을 거치지 않는다면 이미 만들어진 페이지에는 변화를 줄 수 없습니다.

그래서 동적 컨텐츠를 포함한다면 SSG 를 사용하지 않습니다.

 

그렇다면 동적 컨텐츠를 만들 때 어떤 방식을 사용해야 될까요? 

CSR , SSR , ISR 를 사용하면 됩니다. 하지만 이 세가지 방법에도 차이점이 있습니다.

 

CSR [ Client Side Rendering ]

서버에서 처리 없이 클라이언트로 html, css, javascript 등 모든 파일을 보내주고 Client 측에서 렌더링한다

 

장점 :

서버에 부담을 줄일 수 있다

초기에 화면을 모두 그려주고 서버에서 Json 데이터를 받아오면서 화면을 그려주므로 서버에 부담을 줄인다

 

후속 페이지 로드시간이 빠르다

초기에 그려진 화면에 Javascript 를 사용해서 동적으로 DOM을 그려내기 때문에 원하는 내용만 업데이트 할 수있습니다.

 

단점 :

초기 로딩속도가 비교적 느리고 SEO(검색 엔진 최적화) 에 취약하다. 

서버에서 처리 없이 클라이언트로 html, css, javascript 등 모든 파일을 보내주고 Client 측에서 렌더링을 하기 때문에 javascript가 모두 다운로드 되고 실행이 끝나기 전까지 사용자는 볼수 있는게 없다. 그러므로 검섹 엔진 크롤러가 해당 페이지에 처음 방문 했을 때는 빈 페이지이기 때문에 이해할 수 없습니다. 물론 구글 크롤러와 같은 검색엔진 크롤러가 등장하고 있지만 아직까지 많은 크롤러들이 지원되지 않습니다.

 

SSR [ Server Side Rendering ]

서버에서 렌더링하여 완성된 HTML 파일을 Client 로 보내 화면을 그려줍니다.

 

장점 :

초기 화면을 빠르게 그려준다

클라이언트에서 요청하면 서버에서 완성된 HTML을 보내주게되므로 CSR 보다 초기 화면을 빠르게 그려줍니다.

 

SEO(검색 엔진 최적화) 에 유리하다.

서버에서 이미 그려진 HTML를 넘겨주므로 페이지에 대한 정보가 모두 담겨있어 SEO에 유리합니다.

 

단점 :

깜빡임 현상, 초기 렌더링을 제외하고 CSR보다 화면을 그려주는데 속도가 느림

링크를 이동하거나 새로고침 할 때 HTML파일 자체를 서버에서 새로 불러오기 때문에 깜빡임 현상이 있습니다.

그러므로 초기 렌더링은 빠를지 몰라도 이후 새로고침이나 링크 이동시에는 CSR 방식보다 렌더링이 느립니다.

또한 SSR은 완성된 HTML를 Javascript 보다 빠르게 불러오므로 화면이 그려진 후에 기능이 동작하지 않을 수 있습니다.

 

서버에 부담을 줍니다

렌더링의 과정이 서버에서 일어나므로 서버에 부담이 커질 수 있습니다.

 

 

ISR [ Incremental Static Regeneration ]

ISR 은 점진적으로 정적 페이지를 다시 생성해준다는 의미로

SSG 의 장점을 살리면서 최신 데이터를 반영할 수 있는 방법 입니다. 

빌드 시점에 페이지를 생성하고 (SSG 와 동일)

일정 시간이 지난 후 최신 데이터로 페이지를 새로 생성합니다.

 

 

 

NextJS 로 코드 구현해보기

 

종류 데이터 취득에 사용하는 함수 데이터 취득 시점
CSR useSWR, useEffect 등 임의의 함수 사용자 요청 시
SSR getServerSideProps 사용자 요청 시
SSG getStaticProps 빌드 시
ISR revalidate를 반환하는 getStaticProps 빌드 시

 

CSR

 

import { useEffect, useState } from "react";

interface User {
  userId: number;
  id: number;
  title: string;
  body: string;
}

export default function Csr() {
  const [users, setUsers] = useState<User[]>([]);

  useEffect(() => {
    const fetchGetData = async () => {
      const res = await fetch("https://jsonplaceholder.typicode.com/posts");
      const users = await res.json();
      setUsers(users);
    };
    fetchGetData();
  }, []);

  return (
    <div>
      <h1> Client Side Rendering [CSR]</h1>
      <ul>
        {users.map((user) => (
          <li key={user.userId}>{user.title}</li>
        ))}
      </ul>
    </div>
  );
}

 

 

SSR

 

interface User {
  userId: number;
  id: number;
  title: string;
  body: string;
}

export default function Ssr({ users }: { users: User[] }) {
  return (
    <div>
      <h1> Server Side Rendering [SSR]</h1>
      <ul>
        {users.map((user) => (
          <li key={user.userId}>{user.title}</li>
        ))}
      </ul>
    </div>
  );
}

export async function getServerSideProps() {
  const res = await fetch("https://jsonplaceholder.typicode.com/posts");
  const users = await res.json();
  return {
    props: {
      users,
    },
  };
}

 

 

SSG

 

interface User {
  userId: number;
  id: number;
  title: string;
  body: string;
}

export default function Ssg({ users }: { users: User[] }) {
  return (
    <div>
      <h1> Static Site Generation [SSG]</h1>
      <ul>
        {users.map((user) => (
          <li key={user.userId}>{user.title}</li>
        ))}
      </ul>
    </div>
  );
}

export async function getStaticProps() {
  // ssr 과 다른점은 getStaticProps 를 사용하고 build 할 때 하단 api 를 호출해서 html 를 만들어 user에게 제공합니다.
  const res = await fetch("https://jsonplaceholder.typicode.com/posts");
  const users = await res.json();
  return {
    props: {
      users,
    },
  };
}

 

 

ISR

 

interface User {
  userId: number;
  id: number;
  title: string;
  body: string;
}

export default function Isr({ users }: { users: User[] }) {
  return (
    <div>
      <h1> Incremental Static Regeneration [ISR]</h1>
      <ul>
        {users.map((user) => (
          <li key={user.userId}>{user.title}</li>
        ))}
      </ul>
    </div>
  );
}

export async function getStaticProps() {
  const res = await fetch("https://jsonplaceholder.typicode.com/posts");
  const users = await res.json();
  return {
    props: {
      users,
    },
    revalidate: 5, // isr 은 ssg에서 선언했던 getStaticProps 에 revalidate 속성을 넣어주게되면 isr 렌더링이 일어나게 됩니다. revalidate 의 값은 '초' 단위의 숫자를 의미합니다. revalidate 값의 시간만큼 지나게되면 이 페이지는 자동으로 다시 그려지게 됩니다
  };
}

 

 

Build

하단의 명령어를 사용하여 Build 를 진행합니다.

yarn build
npm run build

build 후 터미널

 

 

build 이후 터미널을 보면 하단에 렌더링이 어떤 방식으로 되었는지 표현됩니다.

SSR를 표현
CSR 를 표현
SSG,ISR 를 표현

 

이 표시를 토대로 각 파일의 렌더링된 표시를 확인합니다.

ISR 과 SSG 는 같은 표시로 표현되지만 ISR 에는 revalidate 의 값이 함께 표현되고 있습니다.

 

 

렌더링 과정 확인

파일의 렌더링 과정을 확인해봅시다. 하단의 명령어를 골라 터미널에 입력합니다.

npm run dev
yarn dev

각 화면을 띄우고 새로고침을 해보면 CSR은 깜빡임현상이 보이게됩니다.

페이지가 로드되고 API 를 호출하기 때문입니다. 

 

SSR, SSG, ISR 은 새로고침했을 때 깜빡임이 없이 동일하게 보입니다.

 

하지만 데이터를 변경되었을때는 어떨까요?

CSR 은 깜빡임이 있지만 최신데이터가 적용됩니다.

SSR 은 깜빡임 없이 최신데이터가 적용됩니다.

SSG 는 Static 한 파일을 제공하기 때문에 최신 데이터가 반영되지 않습니다.

ISR 은 바로 최신 데이터가 반영되지 않고 revalidate 의 값으로 선언해둔 5초 뒤에 최신데이터가 반영되게 됩니다.