[Next.js] CSR, SSG, SSR, ISR

hyeon·2024년 12월 28일

1. 리액트에서의 CSR

1-1) 간단 정의

CSR: 서버에서 빈 Html을 전달하고 자바스크립트를 이용해서 그림을 그리고 나서 화면에 보여주는 것



1-2) 상세 정의

이미지 출처) 한 입 크기로 잘라먹는 Next.js 강의

  1. 유저가 브라우저를 통해 초기 접속 요청을 서버에게 보냄
  2. 브라우저에게 빈 html 파일을 보냄
  3. 브라우저는 서버로부터 받은 빈 html 파일을 화면에 렌더링(아무것도 나오지 않음)
  4. 서버는 이어서 모든 자바스크립트 파일을 묶어서(영어로 번들링)해서 브라우저로 보냄
  5. 브라우저는 번들링된 자바스크립트 파일들을 직접 실행
  6. 우리가 만든 리액트 컴포넌트들이 실제로 화면에 나타남

✅ 장점:

  • 부드러운 화면 전환: 새로고침 없이 필요한 부분만 갱신해서 SPA 경험이 좋음
  • 백엔드 부담 분산: 렌더링을 클라이언트가 해서 서버는 API 제공에 집중 가능

☑️ 단점:

  • 초기 로딩이 느릴 수 있음: JS 번들 다운로드/실행 끝나야 화면이 제대로 뜸(FCP가 느려짐)
  • SEO에 불리할 수 있음: 크롤러가 JS 렌더링을 못 하면 검색 노출이 약해짐(대응 필요)



2. 사전렌더링(pre-lendering)

2-1) 간단 정의

서버에서 자바스크립트로 미리 html을 그린 후에 가져다 주는 것
= 완성된 html과 추가적 javascript 파일을 브라우저에 전달


서버에서 완성된 html 파일을 전달하는데 자바스크립트 파일은 왜 전달할까?
=> 자바스크립트 파일을 추가로 전달하는 이유는
    "서버가 할 수 없는 작업을 브라우저가 처리하도록 하기 위해서"


서버는 HTML을 미리 만들어서 보낼 수 있지만, 사용자가 직접 상호작용할 수 있는 부분은 서버에서 처리 불가

예를 들어 이벤트(클릭, 스크롤), 버튼을 클릭했을때 팝업이 뜸


[하이드레이션]
즉, HTML은 이미 화면에 보이지만, 클릭이나 입력 같은 사용자와의 상호작용은 아직 동작하지 않는 상태이다.
이때 리액트가 이벤트 핸들러(예: 버튼 클릭할 때 실행되는 onClick)나 상태 관리 로직(useState, useEffect 등)을 연결해서, 화면이 완전히 작동 가능하도록 만드는 작업이 하이드레이션이다.




2-2) 상세 정의

기존 리액트 앱의 FCP(초기 접속 속도)가 느린 문제를 해결하기 위해서 Next.js는 사전렌더링이라고 부르는 새로운 방식의 렌더링을 제공함

이미지 출처) 한 입 크기로 잘라먹는 Next.js 강의!

  1. 유저가 브라우저를 통해 서버에게 초기 접속 요청을 보냄
  2. 서버가 서버측에서 직접 자바스크립트 코드를 직접 실행시켜서 모든 리액트 컴포넌트들을 html로 변환(즉 사전에 렌더링 수행)
  3. 렌더링이 완료된 html 파일을 브라우저에게 전달
  4. 빈 html 파일이 아닌 사전에 렌더링이 완료된 html 파일을 받은 브라우저는 html 파일을 그대로 화면에 렌더링
  5. 아직은 브라우저가 html 파일 하나만 받은 상태여서 인터렉션(상호작용)은 불가능함
  6. 서버가 후속으로 현재 페이지에 해당하는 자바스크립트 코드를 번들링해서 브라우저에게 보냄
  7. 브라우저는 서버로부터 받은 자바스크립트 코드를 직접 실행해서 현재 화면에 렌더링된 html 요소들과 연결시킴
    => 상호작용까지 가능해지는 이 시점을 TTI(Time To Interactive)라고 함



3. 프리페칭

3-1) 간단정의

뷰포트(화면 영역) 안에 링크가 보이면, 그 링크가 가리키는 페이지의 일부 리소스를 미리 로드(prefetch)한다.
사용자가 클릭했을 때 거의 즉각적으로 페이지를 표시할 수 있어 페이지 전환이 자연스러움

Link 태그는 서버 컴포넌트도 마치 CSR처럼 js(RSC Payload)로 불러온다.



3-2) 상세정의

Next.js는 현재 접속요청한 페이지에 해당하는 자바스크립트 코드들만 따로따로 보내주게 됨
    => 근데 이러면, 초기 접속 이후에 발생하게 되는 페이지 이동들은
          클라이언트 사이드 렌더링의 방식으로 추가적인 요청없이
          바로바로 처리 될 수가 없음
    => 다시 페이지를 이동하려고 하면 추가로 자바스크립트 코드를
          불러와야하는 과정이 필요함
    => 이러한 문제를 방지하기 위해서 프리페칭이 나옴



프리페칭: 이동할 수 있는 가능성이 있는 모든 페이지들의 자바스크립트 코드를 미리 불러와 놓는 과정


자바스크립트 번들에는 클라이언트 컴포넌트만 포함되며, 서버 컴포넌트의 데이터는 제외된다.
하지만 대부분의 페이지는 서버 컴포넌트와 클라이언트 컴포넌트가 혼합되어 구성되므로, 서버 컴포넌트의 데이터 없이 클라이언트 컴포넌트만 전달되면 페이지가 정상적으로 렌더링되지 않는다.

이를 해결하기 위해, 서버 컴포넌트를 실행한 결과물(RSC Payload)이 자바스크립트 번들과 함께 브라우저로 전달된다. 브라우저는 전달받은 RSC 페이로드와 자바스크립트 번들을 조합해 최종적으로 페이지를 완성하고 적절히 교체한다.




4. Next.js - 4가지 렌더링 방식 (SSG, SSR, ISR, CSR)

4-1) SSG - 간단정의

프리렌더링의 한 종류: 빌드시 완성된 html을 미리 만듬

  • 사용예시
    - 거의 변하지 않는 콘텐츠: 회사 소개 페이지, 정적 블로그, 문서 사이트

#### 용어정리
(1) 빌드
소스 코드를 실행 가능한 파일로 만드는 과정
컴파일 과정도 포함하고, 그 외에 최적화, 파일 묶기(번들링), 압축 등 웹사이트가 배포 가능한 상태로 만드는 모든 작업을 포함

(2) 컴파일
보통 컴파일은 고급 언어로 작성된 코드를 기계어로 변환을 의미하지만,
=> 자바스크립트에서의 컴파일러는 JSX나 최신 JavaScript 코드(React 코드)를 브라우저가 이해할 수 있는 순수 JavaScript 코드로 변환해줌  


SSG - 상세정의

  1. 빌드 타임에 미리 사전렌더링을 진행해서 페이지를 미리 딱 한번만 생성
  2. 빌드가 완료된 이후에, 브라우저가 접속 요청을 보내게 되면, 미리 만들어두었던 페이지로 응답
  3. 브라우저는 서버로부터 받은 html 페이지를 화면에 렌더링
  4. hydration을 위해서 서버가 js 번들을 후속으로 전달하면, 브라우저에서 이를 실행해서 hydration을 완료함으로써 상호작용이 가능한 완성된 페이지가 됨

✅ 장점:
사전렌더링에 많은 시간이 소요되는 페이지더라도 사용자의 요청에는 매우 빠른 속도로 응답 가능

☑️ 단점:
다시는 페이지를 새롭게 사전렌더링 하지 않기 때문에 최신 데이터 반영은 어렵다.




4-2) SSR - 간단정의

프리렌더링의 한 종류: 요청 시마다 서버에서 최신 데이터를 받아 HTML을 생성해서 브라우저로 전송

  • 사용예시
    - 사용자별 맞춤 페이지: 로그인한 사용자마다 다른 정보를 보여줄 때
    - 실시간 데이터가 중요한 경우: 주식 시세, 실시간 검색 결과, 수강신청이나 티켓팅



SSR - 상세정의


사용법 및 주의사항

const res = await fetch("http://examples.api", {
  cache: "no-store",
});

fetch를 사용할 때 'no-store' 옵션을 주면, 서버 측에서 데이터를 매번 새로 가져오게 된다.
이건 서버 측 캐시를 사용하지 않겠다는 선언이지, 클라이언트 측의 캐시, 특히 Next.js의 Route Cache와는 별개의 이야기다.


그렇다면 문제가 생길 수 있는 상황은❓❓

예를 들어 어떤 페이지에서 매번 최신 데이터를 보여줘야 한다고 하자. fetch'no-store'를 사용했으니 서버에서는 항상 최신 데이터를 가져다준다.

하지만 클라이언트 측에 Route Cache가 남아있다면, 사용자는 여전히 이전 데이터를 보게 된다.
즉, UI에 과거 데이터가 노출되는 문제가 생긴다.


✅ 해결 방법: 캐시 무효화

이럴 때 필요한 게 바로 캐시 무효화다.

Next.js에서는 이를 위한 API를 제공하고 있다:

  • revalidatePath(path: string)
  • revalidateTag(tag: string)

이 API들은 next/cache 모듈에서 import할 수 있으며, 사용 시 서버 캐시뿐 아니라 클라이언트 캐시까지 모두 무효화해준다.

"use server";
import { revalidatePath, revalidateTag } from "next/cache";

type RevalidateOptions =
  | { type: "path"; path: string; kind: "page" | "layout" }
  | { type: "tag"; tag: string };

/**
 * revalidate 함수: 매개변수에 따라 revalidatePath 또는 revalidateTag 실행
 * @param options - revalidation 옵션 (path 또는 tag)
 */
export const revalidate = (options: RevalidateOptions): void => {
  if (options.type === "path") {
    // revalidatePath 호출
    revalidatePath(options.path, options.kind);
  } else if (options.type === "tag") {
    // revalidateTag 호출
    revalidateTag(options.tag);
  }
};



4-3) ISR

간단정의

프리렌더링의 한 종류: 빌드 시 정적 파일을 생성하되, 일정 주기나 조건에 따라 페이지를 백그라운드에서 재생성
=> 새 버전 생성중에도 기존 캐시 버전 제공

  • 사용예시:
    - 주간 베스트셀러 목록, 이벤트 일정 페이지
    - 구인구직 사이트, 부동산 사이트
    => 콘텐츠가 가끔 변경되지만, 매 요청마다 SSR할 필요는 없는 경우


상세정의

  1. ssg 방식으로 빌드 타임에 생성된 정적인 페이지에 마치 음식처럼 유통기한을 설정함
  2. 유통기한 전까지는 매번 똑같은 페이지를 계속해서 응답
  3. 유통기한이 딱 끝나는 순간부터는 서버측에서 다시 새롭게 해당페이지를 정적으로 생성
  4. 그 이후에 발생하는 접속 요청부터는 새로운 페이지를 반환

✅ 장점:
굉장히 빠른 속도로 브라우저에게 응답이 가능함.(기존 SSG의 장점)
주기적으로 페이지를 업데이트하여 최신데이터 반영가능(기존 SSR의 장점)




4-4) Next.js에서의 CSR

프리렌더링 ❌ -> 하지만 Next.js에서는 약간 애매하다.

React는 완전히 빈 HTML을 브라우저에 전달 후 모든 내용을 Javascript로 렌더링한다.
=>Next.js는 최소한의 HTML은 미리 만든 후, 추가 요청이 필요한 데이터만 브라우저에서 렌더링

  • 사용예시
    - SEO가 중요하지 않고, 사용자 상호작용이 많은 SPA 형태의 앱
    (ex: 내부 관리 툴, 대화형 대시보드, 복잡한 프론트엔드 UI 조작이 필요한 앱)
    - 사내용 대시보드: 로그인한 직원만 사용하고, 검색 엔진 노출 필요 없음
    - 그래프, 차트가 많은 분석 툴
    - 무한스크롤





추가) 라우트 핸들러와 서버액션


라우트 핸들러

  1. Next.js와의 밀접한 통합
    • 라우트 핸들러는 Next.js 프로젝트 내에서 자연스럽게 동작하므로, 별도의 Express.js 서버나 추가 설정 없이도 API 엔드포인트를 정의할 수 있다.
  2. 서버리스 환경에 최적화
    • 라우트 핸들러는 기본적으로 서버리스 환경을 염두에 두고 제작되었다.
    • Vercel 같은 서버리스 호스팅 플랫폼에 배포하면, 요청이 있을 때만 해당 함수가 실행되어 자원을 효율적으로 활용할 수 있다.
  3. 다양한 HTTP 메서드 지원
    • GET, POST, PUT, DELETE 등 다양한 HTTP 메서드를 라우트 핸들러 내에서 쉽게 처리할 수 있어, RESTful API 구현이 편리하다.
  4. 미들웨어나 응답 객체와의 편리한 인터페이스:
    • Next.js 내장 NextResponse 객체를 통해 JSON, 쿠키 설정, 헤더 제어 등의 처리가 쉽다.
  5. CORS 우회 및 은닉화
    • 실행 시점이 브라우저가 아니기 때문에 CORS 부터도 자유롭다.
    • 보안 문제(API Key 숨김 등) 이 가능합니다.

🧐 Next.js에서 API 연결 시 라우트 핸들러를 꼭 사용해야 할까?

  • 반드시 그럴 필요는 없음
    • 서버 컴포넌트에서 호출하는 API 는 Server Action 이 자동으로 됨으로, 굳이 라우트핸들러로 다시 한번 은닉화를 시킬 필요는 없다.
    • Server Action 이 더 좋은 선택이다.



서버액션과 라우트핸들러 비교


✅ 공통점

  • 서버에서 동작한다.

차이점

  • route handler
    1. route.ts 라는 파일을 추가로 만들고, method에 맞게 함수를 만들어야 한다.
    2. 외부 프론트엔드에서도 요청 가능
  • server actions
    1. 함수를 만들고, "use server"를 작성한다.
    2. 서버 컴포넌트에서 revalidatePath 와 같은 함수 사용 시 자동으로 데이터 패치 가능
profile
🥕

0개의 댓글