SSR - Server Side Rendering

사용자 요청마다 서버에서 HTML 생성

SSR

  • 요청이 들어올 때 마다 사전 렌더링하는 방식
    → 사용자의 요청이 있을 때마다 서버에서 HTML을 실시간으로 생성하여 응답하는 방식
  • ex) 로그인된 사용자 맞춤 데이터, 실시간 가격 등

장점

  • 항상 최신 데이터를 기반으로 렌더링할 수 있음.
    (사용자 맞춤형 콘텐츠나 자주 변경되는 데이터가 필요한 서비스에 적합)

단점

  • 매 요청마다 서버에서 렌더링하므로 정적 생성에 비해 성능 비용이 큼. → 성능 저하
  • 서버 상태가 안좋거나 데이터 내용이 크면 데이터 응답 상태가 느려져 브라우저 로딩이 느려질 수 있음.
  • CDN 캐싱 문제

SSR 설정

1
export const getServerSideProps = () => {};

위 처럼 getServerSideProps를 설정하면 SSR - Server Side Rendering이 됨.
약속된 함수를 만들어서 export를 했기 때문에 컴포넌트보다 먼저 실행되어 컴포넌트에 필요한 데이터를 불러오는 함수

  1. index 페이지로 요청
    2. getServerSideProps : 서버 요청
    3. 페이지 컴포넌트 실행
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
export const getServerSideProps = () => {
    const data = 'hello';
    
    console.log('서버사이드프롭스입니다.')

    return {
        props: {
            data,
        }
    };
}
  
export default function Home ({
  data
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
  // ...    
}

위 처럼 getServerSideProps 함수의 리턴 값은 단 하나의 객체여야만 함.

getServerSideProps 부터 props 데이터를 전달받는 페이지 컴포넌트의 props 타입의 정의
InferGetServerSidePropsType : getServerSideProps 함수의 반환값 타입을 자동으로 추가

  • getServerSideProps 함수는 사전 렌더링 과정에서 딱 한번만 실행. 오직 서버측에서만 실행되는 함수.
    안에 console.log를 찍어도 서버에서만 실행되기 때문에 브라우저에서 콘솔 메시지가 출력되지 않고 터미널에 콘솔 메시지 출력 됨.

  • getServerSideProps 함수 내부에 브라우저 환경에서만 이용할 수 있는 메서드 (window.location 등)로 접근 시 오류 발생.
    컴포넌트 또한 서버에서 한번 실행 후 브라우저에 한번 더 실행

컴포넌트 또한 서버에서 실행되니 어떠한 조건 없이 window.location 같은 메서드를 사용하면 오류가 발생할 수 있음.
useEffect를 사용하면 해결. (마운트 시점 이후에 실행)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
export const getServerSideProps = () => {
  const data = 'hello';

  return {
    props: {
      data,
    }
  };
}

export default function Home ({
  data
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
  console.log(data);

  useEffect(() => {
    console.log(window);
  }, []);
}

브라우저에서만 콘솔 실행


SSR 사용

API 호출을 담당하는 함수 만들기

1) src → lib 폴더 생성 후 API 호출 함수 생성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
export default async function fetchBooks () : Promise<BookData[]> {
  const url = `http://localhost:12345/book`
  
  try {
    const response = await fetch(url)
    
    if(!response.ok) {
        throw new Error();
    }
    return await response.json()
  }
  catch {
    console.error(error)
    return [];
  }
}
  • 타입은 Promise, 제네릭에는 BookData 타입(기존에 만들어 놓은 타입)을 정의.
  • 여러개의 도서를 반환하기 때문에 return은 Array 타입으로 정의.

2) API 호출 함수를 사용할 파일 생성 (index.ts)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
export const getServerSideProps = async () => {
  const allBooks = await fetchBooks();
  const recoBooks = await fetchRandomBooks();
  
  return {
    props: {
      allBooks,
      recoBooks,
    }
  }
}

export default function Home ({
  allBooks,
  recoBooks,
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
  return (
    <div></div>
  )
}

3) 위와 같은 방식으로 사용하면 전체 도서 목록을 부른 후 랜덤 도서 목록을 부르는 직렬방식으로 동작
병렬방식으로 변환 필요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
export const getServerSideProps = async () => {
  const [allBooks, recoBooks] = await Promise.all([
    fetchBooks(),
    fetchRandomBooks(),
  ])
		
  return {
    props: {
      allBooks,
      recoBooks,
    }
  }
}

export default function Home ({
  allBooks,
  recoBooks
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
  return (
    <div></div>
  )
}

Promise.all → 인수로 전달된 배열 안에 들어있는 모든 비동기 함수들을 동시에 실행