Skip to content

Server-Side Rendering (SSR) - Page Router

SSR은 페이지 요청이 들어올 때마다 서버에서 HTML을 생성하는 렌더링 방식이다.
Next.js Page Router에서는 getServerSideProps를 사용해 SSR 페이지를 구현할 수 있다.


tsx
import SearchableLayout from '@/components/searchable-layout';
import style from './index.module.css';
import { ReactNode, useEffect } from 'react';
import books from '@/mock/books.json';
import BookItem from '@/components/book-items';
import { InferGetServerSidePropsType } from 'next';

// 이 함수가 존재하면 해당 페이지는 SSR로 동작한다.
// 페이지 요청 시 서버에서만 실행된다.
export const getServerSideProps = () => {
  // 페이지 컴포넌트보다 먼저 실행되어, 렌더링에 필요한 데이터를 서버에서 준비한다.
  const data = 'Hello';

  // 반드시 { props: { ... } } 형태의 객체를 반환해야 한다.
  // 반환된 props는 페이지 컴포넌트의 props로 전달된다.
  return {
    props: {
      data,
    },
  };
};

// 서버에서 한 번 실행되어 HTML을 생성한다.
// 이후 브라우저에서 JS 번들이 로드되며, Hydration 과정에서 다시 실행된다.
export default function Home({
  data,
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
  console.log(data);

  // 브라우저에서만 실행된다 (Hydration 이후)
  // 브라우저 전용 API(window, document 등)는 반드시 이 안에서 사용해야 안전하다
  useEffect(() => {
    console.log(window);
  }, []);

  return (
    <div className={style.container}>
      <section>
        <h3>지금 추천하는 도서</h3>
        {books.map((book) => (
          <BookItem key={book.id} {...book} />
        ))}
      </section>
      <section>
        <h3>등록된 모든 도서</h3>
        {books.map((book) => (
          <BookItem key={book.id} {...book} />
        ))}
      </section>
    </div>
  );
}

// - Page Router에서 페이지별 레이아웃을 적용하기 위한 패턴
// - 페이지 컴포넌트를 인자로 받아 레이아웃으로 감싼다
Home.getLayout = (page: ReactNode) => {
  return <SearchableLayout>{page}</SearchableLayout>;
};

getServerSideProps

  • 페이지 요청 시 서버에서 실행되며, 해당 페이지를 SSR로 동작하게 만든다.
  • 반환한 props는 페이지 컴포넌트에 전달된다.
  • 요청마다 실행되므로 캐싱 없이 항상 최신 데이터를 사용한다.

SSR 페이지 컴포넌트의 실행 흐름

  1. 서버에서 컴포넌트 실행 → HTML 생성
  2. 브라우저에서 HTML 수신
  3. JS 로드 후 Hydration → 컴포넌트 재실행

이 때문에 컴포넌트 내부에서는 window, document 같은 브라우저 전용 객체를 바로 사용할 수 없다.


브라우저 전용 코드 처리

브라우저에서만 실행되어야 하는 코드는 useEffect 내부에서 처리한다.


getServerSideProps Context 객체

getServerSideProps는 Next.js가 자동으로 context 객체를 인자로 전달한다.

tsx
export const getServerSideProps = (context) => {
  const { params, query, req, res } = context;

  return { props: {} };
};
속성설명
params동적 라우트 파라미터 ([id].tsxparams.id)
queryURL 쿼리 스트링 (?sort=latestquery.sort)
reqNode.js HTTP 요청 객체
resNode.js HTTP 응답 객체

예시: 동적 라우트에서 데이터 불러오기

pages/books/[id].tsx

tsx
export const getServerSideProps = (context) => {
  const { id } = context.params;

  // id를 이용해 해당 책 데이터를 가져온다
  const book = fetchBookById(id);

  return {
    props: { book },
  };
};

export default function BookDetail({ book }) {
  return <h1>{book.title}</h1>;
}
  • /books/123 요청 시 params.id는 "123"이 된다.
  • getServerSideProps의 context를 활용하면 요청 정보에 따라 동적인 SSR 페이지를 구현할 수 있다.

SSR에서 실제 API 데이터 페칭

SSR에서는 페이지 요청 시점에 서버에서 API를 호출해 렌더링에 필요한 데이터를 미리 준비할 수 있다.

tsx
export const getServerSideProps = async () => {
  // 서로 의존하지 않는 API는 병렬로 호출해 응답 시간을 줄일 수 있다
  const [allBooks, recoBooks] = await Promise.all([
    fetchBooks(),
    fetchRandomBooks(),
  ]);

  return {
    props: {
      allBooks,
      recoBooks,
    },
  };
};
  • getServerSideProps 내부에서 호출된 API는 요청마다 서버에서 실행된다.
  • 여러 API를 호출해야 하는 경우 Promise.all을 사용해 병렬 처리할 수 있다.

쿼리 스트링 기반 검색 SSR

context.query를 사용하면 URL 쿼리 스트링을 서버에서 직접 읽을 수 있다.
이를 활용해 검색 결과 페이지를 SSR로 구현할 수 있다.

tsx
export const getServerSideProps = async (context) => {
  const q = context.query.q;
  const books = await fetchBooks(q as string);

  return {
    props: { books },
  };
};
  • /search?q=react 요청 시 context.query.q는 "react"가 된다.
  • 서버에서 검색 결과를 만들어 HTML에 포함해 응답할 수 있다.

동적 라우트 기반 상세 페이지 SSR

SSR에서는 context.params를 사용해 동적 라우트 값을 처리할 수 있다.
이를 통해 상세 페이지를 요청마다 서버에서 렌더링할 수 있다.

tsx
export const getServerSideProps = async (context) => {
  const id = Number(context.params?.id);
  const book = await fetchOneBooks(id);

  if (!book) {
    return { notFound: true };
  }

  return {
    props: { book },
  };
};
  • /books/123 요청 시 context.params.id는 "123"이 된다.
  • 서버에서 해당 ID에 맞는 데이터를 조회한 뒤 페이지를 렌더링한다.
  • 데이터가 존재하지 않는 경우 notFound: true를 반환해 404 페이지를 표시할 수 있다.

정리

  • SSR은 요청 시점에 서버에서 데이터를 준비해 HTML을 생성한다.
  • 검색, 상세 페이지처럼 요청마다 결과가 달라지는 화면에 적합하다.
  • context.query, context.params를 활용하면 동적인 SSR 페이지를 구현할 수 있다.
  • 요청 정보(쿠키, 헤더, 파라미터)를 기반으로 한 개인화 UI 구현에 적합하다.

SSR은 “요청마다 서버에서 데이터를 준비해 HTML을 생성한다”는 특성 때문에
검색, 상세, 개인화 페이지처럼 요청별로 결과가 달라지는 화면에 특히 잘 어울린다.

개인화 페이지란,

같은 URL이라도 요청한 사용자에 따라 서로 다른 데이터를 보여주는 페이지를 의미하며, 요청 정보를 바로 활용할 수 있는 SSR 방식이 특히 적합하다.