⚛️ React - Next.js App Router에서의 렌더링 전략 이해
Component란?
: 화면에 보여줄 조각
function Hello() {
return <h1>안녕!</h1>;
}
Server Component
: 서버에서 실행됨
ㄴ 브라우저가 아닌 서버에서 렌더링됨
- 민감한 API 키나 DB 접근이 필요한 경우 등에서 서버 컴포넌트로 만듦 (보안성)
// 이건 서버에서 실행됨 (브라우저가 아닌)
// 서버 컴포넌트
export default async function PostList() {
const res = await fetch('https://api.example.com/posts');
const data = await res.json();
return (
<ul>
{data.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
- 서버가 API에 요청해서 글 목록을 받아오고
- 받아온 글을 화면에 보여줘
- 브라우저에서는 ‘화면만 보여줘’, 직접 API에 접근 안 해!
Client Component
: 브라우저에서 실행됨
- 사용자 인터랙션, 애니메이션 등에서 사용
'use client'; // 이건 꼭 써줘야 함!
import { useState } from 'react';
export default function LikeButton() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
좋아요 {count}개
</button>
);
}
- 사용자가 클릭할 수 있고
- 버튼을 누를 때마다 숫자가 올라감
- 이건 브라우저에서만 작동하니까 'use client'를 꼭 써야 해!
'use client';
이 선언이 없는 컴포넌트는 기본적으로 서버 컴포넌트로 취급
use client를 명시하면 브라우저에서 실행되는 클라이언트 컴포넌트가 됨
https://ko.react.dev/reference/rsc/server-components
fetch()
: 브라우저나 서버에서 HTTP 요청을 보내서 데이터(API 등)를 가져오는 함수
어디서 쓰느냐: 위치에 따른 차이
Server Component | 페이지나 서버 전용 파일 | SSR, SSG, ISR 가능 |
Client Component | 'use client' 있는 컴포넌트 | CSR만 가능 |
// 서버에서 쓰는 fetch (가능: SSR/SSG/ISR)
export default async function Page() {
const res = await fetch("https://api.example.com");
const data = await res.json();
return <div>{data.title}</div>;
}
// 클라이언트에서 쓰는 fetch (가능: CSR만)
"use client";
import { useEffect, useState } from "react";
export default function Page() {
const [data, setData] = useState(null);
useEffect(() => {
fetch("https://api.example.com")
.then(res => res.json())
.then(setData);
}, []);
return <div>{data?.title}</div>;
}
어떻게 쓰느냐: fetch 옵션에 따른 전략
1.
fetch("https://api주소.com", { cache: "no-store" });
➡️ SSR (항상 fresh)
2.
fetch("https://api주소.com", { cache: "force-cache" });
➡️ SSG (빌드 타이밍 캐시)
3.
fetch("https://api.com", { next: { revalidate: 10 } });
➡️ ISR (10초마다 재생성)
CSR
: 클라이언트 사이드 렌더링
- useEffect 같은 React hook에서 fetch하여 데이터 받아와 렌더링
- 브라우저에서 JS로 HTML 생성
- JS가 필요하며, 초기 로딩이 느림 (즉 첫 화면이 늦게 뜨지만 이후 페이지 전환은 빠름)
- 대시보드나 SPA 등 사용자 상호작용이 많은 경우에 적합
"use client";
import { useEffect, useState } from "react";
export default function CSR() {
const [data, setData] = useState();
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/posts/4")
.then(res => res.json())
.then(setData);
}, []);
return <p>{data?.title}</p>;
}
- setData는 이 상태를 변경할 때 사용하는 함수
- API 요청으로 받아올 데이터를 이 변수에 저장
SSR
: 서버 사이드 렌더링
: 웹 페이지를 서버측에서 렌더링하는 방식
- 유저의 매 페이지 요청마다 서버에서 HTML 생성됨
- 매번 새로운 데이터 받아옴!
- 항상 최신 상태를 유지해야하는 웹 페이지나, 분석 차트 등에서 적합 (로그인이나 프로필)
CSR과의 차이로는 CSR은 useEffect 사용,
SSR에서는 getServerSideProps를 활용하여 데이터를 불러옴
getServerSideProps
- Next.js에서 pre-rendering 중 컴포넌트 함수 호출 전에 getServerSideProps를 먼저 호출
- API 통신을 통해 데이터를 받아온 후 컴포넌트에 props로 데이터를 전달
- 매 요청마다 호출되며 서버에서 실행됨
export async function getServerSideProps(context) {
const res = await fetch(`https://.../data`);
const data = await res.json(); //서버가 보내준 실제 데이터
return {
props: {
listData: data,
},
};
}
const Main = ({ listData }) => {
<ul>
{listData.map((item) => (
<li key={item.id}>{item.title}</li>
))}
</ul>;
};
- 외부 API(https://.../data)에 요청해서 데이터를 받아오고 (fetch)
- 받아온 데이터를 listData라는 이름으로 페이지 컴포넌트에 props로 넘김
SSG
: Static Site Generation
: 빌드를 진행할 때 pages 폴더에서 작성한 각 페이지들에 대해 각각의 문서를 생성해서 static한 파일로 생성
- 정적이고 빠르다!
- 빌드시점에 미리 페이지를 렌더링➡️ 정적인 사이트를 구축할때 좋은 효율
- 정적으로 생성된 정보를 요청마다 동일한 정보로 반환하는 경우에 적합 (소개페이지나 블로글 글 등)
- SSR보다 SSG가 높은 성능?!
(SSG는 빌드 시에 HTML이 생성되고 매 요청마다 HTML을 재사용
하지만 그에 비해 SSR은 매 요청마다 HTML을 생성하기 때문에 응답 속도가 느리고 서버에 더 많은 부담이 감)
getStaticProps와 getStaticPath 사용 (SSG, ISR)
- getStaticProps: 페이지를 미리 생성할 때 필요한 데이터를 가져오는 함수
- getStaticPaths: 동적 라우팅에서 어떤 경로들(path)을 빌드할지 미리 지정
getStatixProps
- SSG를 사용하여 데이터를 받아오려면 getStaticProps를 사용
ㄴ 서버 측에서만 실행되는 함수로 클라이언트에서 실행되지 않음
ㄴ API와 같은 외부 데이터를 받아서 Static Generation 하기 위한 용도
ㄴ 빌드 시에 딱 한 번만 호출
ㄴ static file로 빌드
export async function getStaticProps() {
const res = await fetch(`https://.../data`);
const data = await res.json();
return {
props: {
listData: data,
},
};
}
const Main = ({ listData }) => {
<ul>
{listData.map((item) => (
<li key={item.id}>{item.title}</li>
))}
</ul>;
};
Main 페이지가 호출되면 getStaticProps가 먼저 실행되며 axios 통신을 통해 게시물 리스트를 가져오고
props에 리턴 값을 담아서 Main 컴포넌트에 전달)
+ 동적 경로를 위한 페이지 경로 목록 생성시 getStaticPaths 사용
export const getStaticPaths = async () => {
return {
paths: [
{ params: { id: '1' } },
{ params: { id: '2' } },
{ params: { id: '3' } },
],
fallback: true,
};
};
export const getStaticProps = async ({ params }) => {
const id = params.id;
const res = await axios.get(`https://url/${id}`);
return {
props: {
listData: res.data,
},
};
};
const Detail = ({ listData }) => {
<ul>
{listData.map((item) => (
<li key={item.id}>{item.title}</li>
))}
</ul>;
};
- 호출 순서는 getStaticPaths -> getStaticProps -> Detail 페이지
- getStaticPaths에서 리턴값으로 path에 1,2,3 페이지 번호를 지정
- 그 후 getStaticProps에서 params.id를 읽어서 해당 게시글에 대한 데이터를 가져와서 페이지를 생성
- getStaticPaths에서 정적으로 지정했기 때문에 1,2,3 페이지는 static file로 생성됨
ISR
: SSG에 포함되는 개념 (SSG와의 차이는 설정한 시간마다 페이지를 새로 렌더링)
- SSG는 빌드 시에 페이지를 생성하기 때문에 데이터가 변경되면 다시 빌드를 해야하지만, ISR은 일정 시간마다 특정 페이지만 다시 빌드하여 페이지를 업데이트
- 일정 주기로 갱신
- 블로그와 같이 컨텐츠가 동적이지만 자주 변경되지 않는 사이트에 적합
next: {revalidate: N}
: " 이 페이지를 N초마다 자동으로 새로 고치게 해줘" 라는 뜻
- 정적으로 만든 페이지를 일정 시간 후에 다시 생성하게 만드는 옵션
ㄴ 항상 최신 데이터를 보여줄 수 있음
- getStaticProps()나 fetch()를 사용한 페이지는 기본적으로 SSG로 처리되어 한 번 만들어지면 변하지 않으나, 이걸 일정시간마다 다시 새로 만들고 싶을 때 revalidate 사용
// 빌드 시 데이터를 미리 받아서 페이지에 props로 넘김
export const getStaticProps = async ({ params }) => {
const id = params.id;
const res = await axios.get(`https://url/${id}`);
return {
props: {
list: res.data,
},
revalidate: 20, //페이지를 20초마다 백그라운드에서 새로 생성
};
};
const Detail = ({ list }) => {
<ul>
{list.map((item) => (
<li key={item.id}>{item.title}</li>
))}
</ul>;
};
(해당 코드에서는 20초마다 최신 내용으로 바뀜)
https://velog.io/@kyeun95/CSR-SSR-SSG-ISR-이란
[React] CSR, SSR, SSG, ISR 이란?
클라이언트 사이드 렌더링은 HTML 파일을 받아와서 Client(웹 브라우져)에서 렌더링이 일어나는 방식이다.브라우저의 요청을 서버로 보내면, 서버는 HTML 파일과 함께 JavaScript 파일을 보낸다. 클라
velog.io
코드 예시
https://enjoydev.life/blog/nextjs/1-ssr-ssg-isr
Next.js의 렌더링 방식 이해하기 - SSR, SSG, ISR
Next.js의 렌더링 방식의 차이점에 대해서 알아보며 필요에 따라 적절한 렌더링 기법을 적용하는 방법에 대해 배워보겠습니다.
enjoydev.life