사전 렌더링 (Pre-rendering)과 데이터 페칭
Next.js는 페이지를 렌더링하기 전에 서버에서 미리 데이터를 가져와 HTML을 생성하는 사전 렌더링을 지원합니다. 이를 통해 페이지 로딩 속도를 향상시키고, 검색 엔진 최적화(SEO)에도 유리해집니다.
SSR
사전 렌더링 방식 중 하나인 **SSR (Server-Side Rendering)**은 매 요청마다 서버에서 페이지를 렌더링하는 방식입니다. Next.js에서 SSR을 사용하려면 페이지 컴포넌트에서 getServerSideProps 함수를 export하면 됩니다.
getServerSideProps
getServerSideProps는 페이지 컴포넌트가 렌더링되기 전에 서버에서 실행되는 함수입니다. 이 함수는 컴포넌트에 필요한 데이터를 미리 불러와 props로 전달하는 역할을 합니다
// getServerSideProps는 미리 약속된 이름입니다.
export const getServerSideProps = () => {
// 💡 이 함수는 브라우저가 아닌 서버 환경에서 실행됩니다.
// 따라서 window, document 등 브라우저 API를 사용하면 에러가 발생합니다.
console.log("여기는 서버에서 실행돼요.");
const data = "hello";
// 💡 반드시 props를 포함한 객체를 반환해야 합니다.
return {
props: {
data,
},
};
};
⚠️ 주의할 점
- getServerSideProps는 서버에서만 실행되므로, window.location과 같은 브라우저 전용 기능은 사용할 수 없습니다.
- 브라우저에서 동작하는 코드는 useEffect Hook을 사용하여 컴포넌트가 렌더링된 이후에 실행되도록 해야 합니다.
InferGetServerSidePropsType를 이용한 타입 정의
TypeScript를 사용하는 경우, getServerSideProps가 반환하는 props의 타입을 수동으로 정의할 필요 없이 InferGetServerSidePropsType를 사용하여 자동으로 추론할 수 있습니다. 이렇게 하면 타입 불일치로 인한 오류를 방지하고 개발 생산성을 높일 수 있습니다.
import { useEffect } from "react";
// 💡 getServerSideProps의 반환 타입을 자동으로 추론하여 설정해줍니다.
import { InferGetServerSidePropsType } from "next";
// Home 컴포넌트는 getServerSideProps에서 반환된 props를 받습니다.
export default function Home({
data,
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
// "hello"가 출력됩니다.
console.log(data);
// 💡 useEffect는 컴포넌트가 렌더링된 후 브라우저 환경에서 실행됩니다.
useEffect(() => {
// 따라서 이 안에서는 window 객체에 접근할 수 있습니다.
console.log(window);
}, []);
return <h1>{data}</h1>;
}
서버사이드 랜더링 관련 코드 중
const [allBooks, recoBooks] = await Promise.all([
fetchBooks(),
fetchRandomBooks()
]);
- 이렇게 하면 fetchBooks, fetchRandomBooks 를 병렬로 실행하여 좀 더 빠르게 결과를 볼 수 있다.
const id = context.params!.id; // ! 이건 무조건 id가 있을거라고 단언 하는 것!
export default async function fetchOneBook(id:number) : Promise<BookData | null> {
- 비동기 통신이기 때문에 Promise 이걸 써준다
💡
서버사이드 랜더링은 페이지 내부의 데이터를 항상 최신으로 유지하는 장점이 있지만 만약 서버 통신 시 데이터를 늦게 받을 경우 모든 게 늦어지는 단점이 있다.
그래서 정적 사이트 생성 (SSG) 방식이 있다!!
정적 사이트 생성 (SSG: Static Site Generation)
- **정적 사이트 생성(SSG)**은 Next.js의 사전 렌더링 방식 중 하나로, 빌드 타임에 모든 페이지를 미리 HTML 파일로 생성합니다. 이렇게 생성된 정적 파일은 CDN에 배포되어 사용자 요청에 매우 빠르게 응답할 수 있습니다.
SSG의 특징
- 장점: 페이지 로딩 속도가 매우 빠르고, 서버 부하가 적으며, CDN 활용에 유리합니다.
- 단점: 빌드 이후 데이터가 변경되어도 페이지가 업데이트되지 않습니다. 항상 동일한 페이지를 보여주므로, 자주 변경되는 데이터가 있는 페이지에는 적합하지 않습니다.
getStaticProps를 이용한 데이터 페칭
SSG를 사용하려면 페이지 컴포넌트에서 getStaticProps 함수를 export해야 합니다. 이 함수는 빌드 시점에 딱 한 번만 실행되어 데이터를 가져오고, 그 데이터를 컴포넌트에 props로 전달합니다.
// getServerSideProps와 달리, getStaticProps는 빌드 시점에 실행됩니다.
export const getStaticProps = async () => {
// 빌드 시점에 백엔드 API를 호출하여 데이터를 가져옵니다.
const [allBooks, recoBooks] = await Promise.all([
fetchBooks(),
fetchRandomBooks()
]);
return {
props: {
allBooks,
recoBooks
}
};
};
// getServerSideProps에서 사용했던 InferGetServerSidePropsType와 마찬가지로
// InferGetStaticPropsType을 사용하여 props의 타입을 자동으로 추론할 수 있습니다.
import { InferGetStaticPropsType } from "next";
export default function Home({
allBooks,
recoBooks
}: InferGetStaticPropsType<typeof getStaticProps>) {
// ... 컴포넌트 로직
}
💡 참고: getStaticProps를 export 하지 않으면 해당 페이지는 기본적으로 정적 페이지로 생성됩니다. (데이터 페칭 로직이 없는 경우)
SSG와 동적 경로 (Dynamic Routes)
Next.js에서 [id].tsx와 같은 동적 경로를 SSG로 사용하려면, 빌드 시점에 어떤 경로를 정적으로 생성할지 미리 알려줘야 합니다. 이때 사용하는 함수가 getStaticPaths입니다.
getStaticPaths는 getStaticProps와 함께 사용되며, 생성할 경로들의 params를 정의하는 역할을 합니다.
export const getStaticPaths = () => {
return {
// paths 배열에 정적으로 생성할 경로의 매개변수(params)를 설정합니다.
paths: [
{ params: { id: "1" } }, // -> /1 경로에 대한 페이지 생성
{ params: { id: "2" } }, // -> /2 경로에 대한 페이지 생성
{ params: { id: "3" } }, // -> /3 경로에 대한 페이지 생성
],
// fallback은 paths에 정의되지 않은 경로에 대한 처리 방식을 설정합니다.
fallback: false,
};
};
fallback 옵션 이해하기
- fallback: false: paths에 정의된 경로 외에는 모두 404 페이지를 보여줍니다.
- fallback: 'blocking': paths에 없는 경로로 접근 시, Next.js는 해당 페이지를 서버에서 SSR 방식으로 렌더링한 후 사용자에게 반환하고, 이후 해당 페이지를 정적 파일로 저장합니다. 다음 요청부터는 정적 파일이 제공됩니다.
- fallback: true: paths에 없는 경로로 접근 시, Next.js는 즉시 데이터가 없는 빈 페이지를 보여주고, 백그라운드에서 데이터를 가져와 클라이언트 측에서 렌더링합니다. 사용자 경험을 위해 로딩 상태를 표시하는 코드를 추가하는 것이 좋습니다.
정적 페이지 내에서 클라이언트 측 데이터 페칭
페이지 자체는 정적 SSG로 생성하되, 쿼리 스트링(?q=...)에 따라 동적으로 데이터를 불러와야 하는 경우가 있습니다. 이런 경우 getStaticProps를 사용하지 않고, 컴포넌트 내부에서 useEffect와 useRouter를 사용하여 클라이언트 측에서 데이터를 가져올 수 있습니다.
import { useRouter } from "next/router";
import { useState, useEffect } from "react";
export default function Page() {
const [books, setBooks] = useState([]);
const router = useRouter();
const q = router.query.q; // 쿼리 스트링 값을 가져옵니다.
const fetchSearchResult = async () => {
// 쿼리 값(q)을 이용해 API를 호출합니다.
const data = await fetchBooks(q as string);
setBooks(data);
};
useEffect(() => {
if (q) {
fetchSearchResult();
}
}, [q]);
}
이 방식은 페이지의 기본 구조는 정적으로 유지하면서, 특정 데이터만 클라이언트에서 동적으로 업데이트할 수 있게 해줍니다.
증분 정적 재생성 (ISR: Incremental Static Regeneration)
ISR은 SSG의 장점(빠른 응답 속도, 낮은 서버 부하)을 유지하면서, 데이터 변경 시 페이지를 다시 생성할 수 있는 방식입니다. SSG가 빌드 시에만 페이지를 한 번 생성하고 끝나는 반면, ISR은 페이지에 "유통기한"을 설정하여 주기적으로 최신 데이터로 업데이트할 수 있습니다.
ISR 설정하기: revalidate 옵션
getStaticProps 함수에 revalidate 옵션을 추가하여 페이지의 유효 기간을 설정할 수 있습니다. revalidate 값은 초 단위로 지정하며, 이 시간이 지나면 페이지에 대한 다음 요청이 들어왔을 때 백그라운드에서 페이지를 다시 생성합니다.
export const getStaticProps = async () => {
const [allBooks, recoBooks] = await Promise.all([
fetchBooks(),
fetchRandomBooks()
]);
return {
props: {
allBooks,
recoBooks
},
// 💡 3초가 지나면 페이지를 재검증하고, 필요 시 다시 생성합니다.
revalidate: 3,
}
};
동작 방식
- 사용자가 처음 페이지에 접속하면, 캐시된 정적 페이지를 즉시 보여줍니다.
- revalidate에 설정된 시간이 지나고 다음 요청이 들어오면, 사용자는 여전히 캐시된 페이지를 받습니다.
- 그와 동시에 Next.js는 백그라운드에서 새로운 페이지를 생성합니다.
- 새로운 페이지 생성이 완료되면, 다음 요청부터는 최신 데이터가 반영된 페이지가 제공됩니다.
주문형 재검증 (On-Demand ISR)
일반적인 ISR은 revalidate 값에 따라 주기적으로 페이지를 업데이트합니다. 하지만 블로그 글 수정이나 상품 등록처럼 특정 이벤트가 발생했을 때 즉각적으로 페이지를 업데이트해야 하는 경우가 있습니다.
이럴 때 **주문형 재검증(On-Demand ISR)**을 사용합니다.
SSR은 매 요청마다 페이지를 생성하기 때문에 서버에 부하가 크고 응답 시간이 느려질 수 있습니다.
On-Demand ISR은 필요한 순간에만 페이지를 재생성하여 SSR의 단점을 보완합니다.
On-Demand ISR 설정하기
API 라우트를 만들어 특정 API가 호출될 때 원하는 페이지를 즉시 재 검증하도록 설정할 수 있습니다.
// 파일 경로: pages/api/revalidate.ts
import { NextApiRequest, NextApiResponse } from "next";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
try {
// 💡 res.revalidate('/')를 호출하여 '/' 경로의 페이지를 즉시 재검증합니다.
await res.revalidate('/');
return res.json({ revalidated: true });
} catch (err) {
console.error("Revalidation error:", err);
return res.status(500).send("Revalidate Failed");
}
}
이 API는 백엔드에서 데이터가 업데이트되었을 때(예: CMS에서 게시글을 수정했을 때) 호출하도록 설정하여, 즉각적으로 최신 페이지를 생성할 수 있습니다.
SEO 설정하기 (배포 사전 준비)
<Head>
<title>한입북스</title>
<meta property="og:image" content="/thumbnail.png" />
<meta property='og:title' content="한입북스" />
<meta property='og:description' content="한입북스에 등록된 도서들을 만나보세요" />
</Head>
if (router.isFallback) {
return <>
<Head>
<title>한입북스</title>
<meta property="og:image" content="/thumbnail.png" />
<meta property='og:title' content="한입북스"/>
<meta property='og:description' content="한입 북스에 등록된 도서들을 만나보세요" />
</Head>
<div>로딩중입니다</div>
</>;
}
동적 방식 SSG 인 경우
- “로딩중입니다.” 텍스트를 보여줄 때 이땐 Head 가 없어서 meta 정보가 반영이 안된다.
- 그래서 isFallback 에 default Head 정보를 포함해준다.
'기술, 개발 > nextjs' 카테고리의 다른 글
| 6차 강의 노트 (데이터 페칭) (0) | 2025.08.28 |
|---|---|
| 5차 강의 노트 (앱라우터) (0) | 2025.08.28 |
| 4차 강의 노트 (앱라우터) (1) | 2025.08.28 |
| 2차 강의 노트 (레이아웃, 스타일링) (1) | 2025.08.28 |
| 1차 강의 노트 (페이지라우터, 프리패칭) (0) | 2025.08.28 |