Ch 02. 서버에서 완성해 보내주다 — SSR
손님이 오기 전에 상을 차린다
식당에 비유해보겠습니다. 손님이 들어서는 순간, 이미 음식이 차려진 테이블이 있습니다. 앉자마자 바로 먹을 수 있습니다. 기다릴 필요가 없습니다.
SSR(Server-Side Rendering)이 바로 이런 방식입니다. 사용자가 페이지를 요청하는 순간, 서버는 그 요청에 맞는 HTML을 즉시 만들어 완성된 형태로 보내줍니다. 브라우저는 받은 HTML을 그대로 화면에 표시합니다. 자바스크립트가 로드되기 전에도 내용을 볼 수 있습니다.
요청마다 서버에서 HTML을 생성한다
SSR의 핵심은 요청마다(per-request) HTML을 생성한다는 점입니다. 사용자 A가 페이지를 열면 서버에서 A를 위한 HTML을 만들고, 사용자 B가 같은 페이지를 열면 서버에서 B를 위한 HTML을 만듭니다. 같은 URL이라도 요청하는 시점, 로그인 상태, 기타 조건에 따라 다른 HTML이 생성될 수 있습니다.
이 덕분에 SSR은 항상 최신 데이터를 보여줄 수 있습니다. 데이터베이스에 1분 전에 추가된 게시글도, SSR 페이지를 새로 고침하면 바로 나타납니다.
장점과 단점
SSR의 장점은 명확합니다.
첫째, SEO(검색엔진 최적화)에 유리합니다. 구글 등의 검색 크롤러는 자바스크립트를 실행하지 않거나, 실행하더라도 불완전하게 처리하는 경우가 있습니다. SSR은 이미 완성된 HTML을 제공하므로 크롤러가 내용을 제대로 인식할 수 있습니다.
둘째, 첫 화면 표시(FCP, First Contentful Paint)가 빠릅니다. 브라우저가 자바스크립트를 파싱하고 실행하기 전에도 사용자는 내용을 볼 수 있습니다.
셋째, 항상 최신 데이터를 보여줍니다. 매 요청마다 데이터를 새로 가져오므로 오래된 정보가 표시될 걱정이 없습니다.
단점도 있습니다.
서버 부하가 증가합니다. 요청마다 서버에서 연산이 발생하므로 트래픽이 많을수록 서버 자원이 많이 필요합니다. 정적 파일을 그대로 내려주는 CDN과 달리, 매번 서버가 일을 해야 합니다.
또한 응답 속도가 서버 처리 시간에 의존합니다. 데이터베이스 쿼리가 느리면 페이지 자체가 느려집니다.
Next.js에서 SSR 사용하기
Next.js App Router에서는 Server Component 안에서 fetch를 사용할 때 cache: 'no-store' 옵션을 주면 SSR처럼 동작합니다. 매 요청마다 데이터를 새로 가져옵니다.
// 파일: app/news/page.tsx
interface Article {
id: number;
title: string;
content: string;
publishedAt: string;
}
async function getLatestArticles(): Promise<Article[]> {
const res = await fetch('https://api.example.com/articles', {
cache: 'no-store', // 캐시하지 않고 매 요청마다 새로 가져옴
});
if (!res.ok) {
throw new Error('기사를 불러오지 못했습니다.');
}
return res.json();
}
export default async function NewsPage() {
const articles = await getLatestArticles();
return (
<main>
<h1>최신 뉴스</h1>
<ul>
{articles.map((article) => (
<li key={article.id}>
<h2>{article.title}</h2>
<time>{article.publishedAt}</time>
</li>
))}
</ul>
</main>
);
}
```text
이 컴포넌트는 Server Component입니다. `async` 함수로 선언되어 있고, `await`로 데이터를 기다립니다. `cache: 'no-store'`가 있으므로 페이지를 요청할 때마다 API를 새로 호출합니다.
페이지 전체를 동적으로 만들고 싶다면 `export const dynamic = 'force-dynamic'`을 파일에 추가할 수도 있습니다.
```tsx
// 파일: app/news/page.tsx
export const dynamic = 'force-dynamic'; // 이 페이지는 항상 SSR로 처리
export default async function NewsPage() {
// ...
}
언제 SSR을 써야 할까
- 로그인한 사용자의 개인화 페이지 (내 정보, 내 주문 내역)
- 실시간성이 중요한 데이터를 보여주는 페이지 (뉴스, 주식 시세)
- 검색 결과 페이지 (검색어에 따라 결과가 달라짐)
- 인증이 필요한 페이지
다음 챕터에서는
다음 챕터에서는 반대편 이야기를 합니다. 서버가 아니라 브라우저가 직접 화면을 그리는 방식, CSR을 알아봅니다. 리액트가 기본적으로 동작하는 방식이기도 하고, "use client" 컴포넌트가 바로 이 방식을 사용합니다.