본문 바로가기

TIL

렌더링 패턴 SSG, SSR, CSR, ISR

■ SSG (Static Side Generation)

 특징  미리 html 파일을 만든 후 브라우저에 보여주는 형태의 렌더링 방식, 최초 빌드 시에만 생성이 됨

 (* 사전에 미리 정적 페이지를 여러 개 만들어놓음 → 클라이언트가 홈페이지 요청을 하면, 서버에서는 이미 만들어져있는 사이트를 바로 제공! → 클라이언트는 표기만 함)

장점 ➜ 첫 페이지 로딩 시간이 매우 짧아(TTV) 사용자가 빠르게 페이지를 볼 수 있음, SEO에 유리, CDN(Content Delivery Network) 캐싱 가능

 단점  정적인 데이터에만 사용할 수 있음, 사용자와의 상호작용이 서버와의 통신에 의존하므로 클라이언트 사이드 렌더링보다 상호작용이 느릴 수 있음, 또한, 서버 부하가 클 수 있다, 마이페이지 처럼 데이터에 의존하여 화면을 그려주는 경우 사용이 불가함

 

☀︎ SSG 으로 데이터 불러오기

next.js 에서 기본적으로 아무것도 설정해주지 않았다면 SSG 방식으로 동작함

yarn build && yarn start

* build : production 레벨로 배포하기 전 필요한 빌드 작업 과정을 실행하기 위한 명령어

* start : 만들어진 build 파일을 이용하여 실행시키는 명령어

build, start 후 브라우저 주소로 접속하면 초기에 홈페이지를 로드하고 나서는 다시 로드하지 않음

 

데이터 불러올 시 리액트에서 사용했었던 useEffect 를 사용할 필요가 없음

→ 왜? 서버컴포넌트안에서는 비동기함수를 직접 사용할 수 있기 때문!

* next.js서버 컴포넌트는 최근에 추가된 fetch api 통해 최적화하므로 axios 보다는 fetch api 사용을 권장

// SSG : 아무것도 하지 않으면 SSG!
import { Product } from "./types";

export default async function Home() {
  const res = await fetch("http://localhost:4000/products");
  const data: Product[] = await res.json();
  
  return (
    <div>
      <h1>Products</h1>
      <div className="p-8 m-4">
        {data.map((product) => (
          <div className="flex border p-4 gap-4 rounded-md" key={product.id}>
            <img
              className="rounded-sm"
              width={150}
              height={150}
              src={product.images}
              alt={product.title}
            />
            <div className="flex flex-col justify-between">
              <div>
                <h2 className="text-xl font-bold">{product.title}</h2>
                <p className="text-sm">{product.description}</p>
                <p className="mt-4 text-2xl">{product.price.amount}$</p>
              </div>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

 

 

■ SSR (Server Side Rendering)

특징  SSG, ISR처럼 렌더링 주체가 서버!, 클라이언트의 요청 시 렌더링(C: 이페이지줘!, S: 데이터읽고...등등 후 html 파일 제공)

 장점 ➜ 빠른 로딩 속도(TTV)와 높은 보안성을 제공, SEO 최적화 좋음, 실시간 데이터를 사용, 마이페이지 처럼 데이터에 의존한 페이지 구성 가능

 단점 ➜ 사이트의 콘텐츠가 변경되면 전체 사이트를 다시 빌드해야 하는데, 이 과정이 시간이 오래 걸릴 수 있음 → 서버 과부하 , 요청할 때 마다 페이지를 만들어야 함

 

☀︎ SSR 으로 데이터 불러오기

방법 (1)

fetch에 옵션 주기

cache: "no-store" 를 추가

* SSR 을 다시 SSG 로 변경 시 cache: "force-cache" 로 바꿔주면 됨.(cache 옵션 미 기재시에도 기본값이 force-cache 임)

방법 (2)

page.tsx 컴포넌트에 dynamic 추가하기(cache 옵션은 삭제)

export const dynamic = "force-dynamic";

// SSR
export default async function Home() {
  // fetch의 url 뒤에 옵션 cache: "no-store" 추가
  const res = await fetch("http://localhost:4000/products", {
    cache: "no-store",
  });
  const data: Product[] = await res.json();
  console.log("render");

  return (
    <div>
      ⋮
    </div>
  );
}

 

fetch 안에서 캐싱이 되지 않아 새로고침 시 렌더링이 새로 된다. (새로고침 시 console.log("render")가 계속 출력됨)

 

 

■ CSR (Client Side Rendering)

 특징  브라우저에서 JavaScript를 이용해 동적으로 페이지를 렌더링하는 방식, 렌더링의 주체 - 클라이언트

 장점 (최초 한번 로드가 끝나면) 사용자와의 상호작용이 빠르고 부드럽다, 서버에게 추가적인 요청을 보낼 필요가 없기 때문에,

            사용자 경험이 좋다, 서버 부하가 적다.

 단점  첫 페이지 로딩 시간(Time To View)이 길 수 있다, JavaScript가 로딩 되고 실행될 때까지 페이지가 비어있어

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

 

☀︎ CSR 으로 데이터 불러오기

① 서버 컴포넌트가 아닌 클라이언트 컴포넌트를 생성해줘야한다. (app/_components/ProductList.jsx)

② 생성한 ProductList 의 useEffect 안에서 fetchData를 불러준다.

★ 클라이언트 컴포넌트 최상단에 "use client" 옵션 꼭 넣어줄 것!

* 클라이언트 로직인 useEffect, useState 는 무조건 클라이언트에서만 동작해야만 한다.

"use client";

import { useEffect, useState } from "react";
import { Product } from "../types";

const fetchData = async () => {
  const res = await fetch("http://localhost:4000/products");
  const data: Product[] = await res.json();
  return data;
};

const ProductList = () => {
  const [data, setData] = useState<Product[]>([]);

  useEffect(() => {
    console.log("render");
    fetchData().then(setData);
  }, []);
  
  return (
    <div>
      ⋮
    </div>
  );
};

export default ProductList;

 

③ 생성한 클라이언트 컴포넌트를 import 해준다.

// 최상단의 page.tsx
import ProductList from "./_components/ProductList";

export default async function Home() {
  return (
    <div>
      <ProductList />
    </div>
  );
}

 

④ 새로 yarn build && yarn start 를 해보면 서버가 아닌 클라이언트 콘솔에서 console.log("render") 가 출력되는 것을 확인할 수 있다.

 

 

■ ISR (Incremental Static Regeneration)

* 특정 타임에만 렌더링이 다시 되도록 하는 SSG와 SSR이 섞여져 있는 렌더링 방식

 특징  SSG처럼 정적 페이지를 제공, 설정한 주기만큼 페이지를 계속 생성해 줌, 정적 페이지를 먼저 보여주고, 필요에 따라 서버에서 페이지를 재생성하는 방식

 장점 ➜ 정적 페이지를 먼저 제공하므로 사용자 경험이 좋으며, 콘텐츠가 변경되었을 때 서버에서 페이지를 재생성하므로 최신 상태를 (그나마) 유지할 수 있음, CDN 캐싱 가능

 단점 ➜ 동적인 콘텐츠를 다루기에 한계가 있을 수 있음(실시간 페이지X), 마이페이지 처럼 데이터에 의존하여 화면을 그려주는 경우 사용 불가

 

☀︎ ISR 으로 데이터 불러오기

방법 (1)

fetch에 옵션 주기

next: {revalidate: } 을 추가해서 ISR을 방식으로 만들어줄 수 있다.

방법 (2)

page.tsx 컴포넌트에 revalidate 추가하기(next 옵션은 삭제)

export const revalidate = 5;

// ISR
import { Product } from "./types";

export default async function Home() {
  const res = await fetch("http://localhost:4000/products", {
    next: {
      revalidate: 3, // 3초 마다 렌더링이 업데이트 됨
    },
  });
  const data: Product[] = await res.json();
  console.log("render");

  return (
    <div>
      ⋮
    </div>
  );
}

 

 

■ 요약

SSG cache: "force-cache"
or
옵션無
ISR next: { revalidate: 5 } // 설정할 주기 입력
or
export const revalidate = 5;
SSR cache: "no-store"
or
export const dynamic = "force-dynamic";
CSR 클라이언트 컴포넌트 최상단에 "use client" 추가,
클라이언트 훅 이용한 코드 작성

 

 

🧐 느낀점

SSG, ISR 등 강의를 듣기만 했을땐 이해하기가 쉽지 않았는데 요약하면서 따라쳐보고 동작원리를 천천히 보다보니 조금 알아가고 있는 것 같다. 아직 갈길이 멀지만..계속 열심히 해보쟈~!