렌더링의 변천사

홍창현·2025년 3월 24일
post-thumbnail

프론트엔드로 웹 페이지를 개발할 때 렌더링에 대해 고려해야 합니다. 요즘 시대 같은 경우 SSR(서버 사이드 렌더링)에 대해 많은 논의가 오가고 필요성이 부각되었습니다.

이번 포스팅에서는 어떤 식으로 사이드 렌더링이 발전했는지 알아보겠습니다.
이번 포스팅의 주된 내용은 다음과 같습니다. 포스팅의 내용이 매우 길 수 있습니다. 2개로 쪼개려고 했으나 흐름을 끊지 않기 위해 하나로 정리했습니다.

  • CSRSSR
  • SPAMPA
  • SSRSSG
  • ISR

SSR은 처음부터 있었다

사실 놀랍게도 SSR은 가장 먼저 등장한 개념입니다. 당시에는 웹 페이지에서 클라이언트와 많은 상호작용을 하던 시기가 아닙니다.

이는 자바스크립트의 등장배경을 보면 알 수 있습니다.
초창기 자바스크립트는 웹 페이지의 보조적인 기능을 수행하기 위해 한정적인 용도로 사용되었습니다. 이 시기에 대부분 로직은 주로 웹 서버에서 실행되었고 브라우저는 서버로부터 전달받은 HTML과 CSS 를 단순히 렌더링하는 수준이었습니다.

그 후로 클라이언트와 웹 페이지의 상호작용이 필요해지며 AJAX(Asynchronous JavaScript and XML)와 같은 기술이 도입되며 웹 페이지는 점차 복잡한 기능들을 담기 시작했습니다.


CSR의 등장

당시에는 웹 애플리케이션의 기능이 다양해지고 사용자가 늘어나면 동시에 서버도 확장해야했지만 클라우드의 개념이 부족했습니다. 그래서 서버를 확장하는 것은 매우 번거로웠고, 서버의 리소스를 사용하는 SSR방식보다는 그러한 부담을 클라이언트에게 넘기는 CSR방식이 인기를 끌게 됩니다.

CSR과 SSR방식의 비교

CSR방식

클라이언트 측에서 JavaScript를 사용하여 동적으로 콘텐츠를 생성하는 방식

작동방식

  1. User가 Website 요청을 보냄
  2. CDN이 HTML파일과 JS로 접근할 수 있는 링크를 클라이언트로 보냄
  3. 클라이언트는 HTML과 JS를 다운받음 → 아직 사용자는 아무것도 볼 수 없음
  4. 다운이 완료된 JS가 실행되고, 데이터를 위한 API를 호출
  5. 서버가 API로부터의 요청을 응답하고 데이터를 클라이언트로 보냄 → 사용자는 placeholder를 볼 수 있음
  6. API로 받아온 데이터를 placeholder자리에 넣어줌

즉, CSR방식은 모든 HTML, CSS, JS를 다운받기까지 아무것도 웹 페이지에 렌더링을 할 수 없습니다.

SSR방식

서버에서 HTML을 생성하여 클라이언트로 전송하는 방식

작동방식

  1. User가 Website 요청을 보냄
  2. Server는 'Ready to Render'. 즉, 즉시 렌더링 가능한 html파일을 만듦
  3. 클라이언트에 전달되는 순간, 이미 렌더링 준비가 되어있기 때문에 HTML은 즉시 렌더링
    그러나 JS가 읽히기 전이므로 사이트 자체는 조작 불가능
  4. 클라이언트가 JS를 다운받음
  5. 다운 받아지고 있는 사이에 유저는 컨텐츠는 볼 수 있지만 사이트를 조작 할 수는 없다. 이때의 사용자 조작을 기억하고 있는다.
  6. 브라우저가 JavaScript 프레임워크를 실행
  7. JS까지 성공적으로 컴파일 되었기 때문에 기억하고 있던 사용자 조작이 실행되고 이제 웹 페이지는 상호작용이 가능

SSR방식의 경우 HTML, CSS, JS가 모두 다운로드 받지 않아도 렌더링 자체는 먼저 일어나게 됩니다.

이는 SPA와 MPA의 특성과도 연결됩니다.

SPA와 MPA의 비교

SPA(Single Page Application)방식

SPA는 말그대로 한개의 페이지로 구성된 애플리케이션입니다. 실제 웹 사이트는 여러개의 페이지가 있지만 한개의 페이지로 운영된다는 뜻입니다.

SPA 특징

  • 서버로부터 완전한 새로운 페이지를 불러오지 않고 현재의 페이지를 동적으로 다시 작성
  • 웹 에플리케이션에 필요한 모든 정적 리소스를 최초 접근 시 단 한번만 다운로드
  • 이후 새로운 페이지 요청 시, 페이지 갱신에 필요한 데이터만을 JSON으로 전달받아 페이지를 갱신 → 기존 페이지의 내부를 수정해서 보여주는 방식

MPA(Multi Page Application)방식

MPA는 말그대로 여러개의 페이지로 구성된 애플리케이션입니다.

MPA 특징

  • 새로운 페이지를 요청할 때마다 서버에서 렌더링된 정적 리소스(HTML, CSS, JavaScript)가 다운로드됨
  • 페이지 이동하거나 새로고침하면 전체 페이지를 다시 렌더링

일반적으로 SPA는 CSR과 많이쓰이고, MPA는 SSR과 많이쓰인다. 하지만 항상 그렇지는 않으니 같은 개념이라고 생각하면 절대 안된다. 특히 Next.js의 경우는 SPA와 SSR을 혼용해서 사용하고 있다.


다시 돌아온 SSR

이렇게 우리가 익숙하게 잘 사용하던 CSR, SPA의 시대가 흘러가다가 다른 문제점에 봉착합니다.

웹사이트가 너무 발전한 것입니다.

자바스크립트 파싱을 위해 CPU를 소비하는 시간이 눈에 띄게 증가합니다. 그래서 웹페이지 로딩도 평균 20초가 걸리고 모바일에서도 웹페이지에서 사용자가 상호작용을 하려면 15초 이상 대기를 해야했습니다.

SPA의 특성때문이기도 하지만 이는 과거에 비해 현재의 웹 애플리케이션이 다양한 작업을 처리하고 있기 때문입니다.

그래서 최초의 사용자에게 보여줄 페이지를 빠르게 서버에서 렌더링해 가져오는 것의 필요성이 생겼습니다.

SSR의 장단점

그렇다면 재등장한 SSR은 어떠한 장단점을 갖고 있는지 확인해봅시다.

장점

1. 최초 페이지 진입이 비교적 빠르다

위에서 확인해본 이유와 같이 최초 페이지 진입이 CSR방식보다 훨씬 빠릅니다.

2. 검색 엔진과 SNS 공유 등 메타데이터 제공이 쉽다

검색 엔진 로봇이 페이지에 진입을 하게 되면, 페이지가 HTML정보를 제공하여 로봇이 다운로드합니다. 하지만, 검색 엔진 로봇은 HTML을 다운로드만 하고 자바스크립트 코드는 실행하지 않습니다.

따라서, 최초 렌더링 작업이 서버에서 일어나는 서버 사이드 렌더링의 경우 HTML 코드를 서버에서 가공이 가능하기 때문에 검색 엔진 최적화에 대응하기가 용이합니다.

서버 사이드 렌더링을 사용한다고 검색 엔진 최적화가 좋다는 것이 아니라 대응이 용이한 것에 유의

3. CLS(Cumulative Layout Shift, 누적 레이아웃 이동)가 적다

CSR의 경우에는 페이지 콘텐츠가 API 요청에 의존하고, 각각 응답속도가 제각각이지만, SSR의 경우는 이러한 요청이 완전히 완료된 이후에 완성된 페이지를 제공하므로 CLS가 적음

→ 위의 경우에 SSR이 더 느릴 수 있음

물론, useEffect와 같이 클라이언트에서 컴포넌트가 마운트된 이후에 실행되는 코드의 경우는 발생할 수 있음

4. 사용자의 디바이스 성능에 자유롭다

5. 보안에 더 유리하다

단점

1. 소스코드를 작성할 때 항상 서버를 고려해야 한다

단순히 서버 사이드 렌더링을 고려해야지! 수준이 아니라 브라우저의 전역 객체인 window 와 같은 전역 객체를 건드리는 경우 오류가 발생함

이 뿐만이 아니라, 라이브러리를 사용하고 있다면, 라이브러리가 서버에 대한 고려가 반드시 되어있어야 하므로 이를 고려해야 함

2. 적절할 서버가 구축돼 있어야 한다

3. 서비스 지연에 따른 문제

CSR의 경우 최초 렌더링에서 지연이 나오는 경우 Suspense를 활용하여 로딩표시를 하여 사용자 경험을 해치지 않을 수 있지만, SSR의 경우 최초 렌더링에서 지연이 생기는 경우 사용자 경험이 안좋아짐

현대의 서버 사이드 렌더링

위에서 본 결과같이 서버사이드 렌더링의 경우 장단점이 명확하다. CSR과 SSR의 장점만을 합친 새로운 프레임워크 Next.js가 인기가 있는 이유이다.

Next.js는 SSR을 활용해 초기 페이지 로딩 속도를 개선하면서도, CSR처럼 클라이언트에서 동적 라우팅을 지원하여 SPA처럼 작동할 수 있습니다. 또한, SSG와 ISR을 통해 정적 페이지 생성을 최적화할 수도 있습니다.


Next.js의 등장 (SSG와 함께)

앞선 SSR의 단점을 해결하고 CSR과 SSR의 장점을 합친 SSG방식의 Next.js가 등장하게 되었다.

그렇다면 SSG는 대체 무엇일까?

SSG(Static Side Generation)방식

SSG방식은 HTML을 빌드 타임에 각 페이지별로 생성하고 해당 페이지로 요청이 올 경우 이미 생성된 HTML 문서를 반환한다.

서버사이드 렌더링과는 살짝 다르다.

서버 사이드 렌더링 : 요청이 올 때 마다 해당하는 HTML 문서를 그때 그때 생성하여 반환

위의 그림과 같이 SSG는 미리 정적 HTML파일을 갖고 있고, 이를 CDN 등에 배포하여 캐시로 저장한다. 여기서 가져온 HTML파일은 hydration과정을 통해 HTML파일에 JavaScript의 이벤트나 상태를 붙여 사용자와 상호작용을 할 수 있다. 즉, 단순한 정적 페이지가 아니란 것이다.

Hydration

서버에서 렌더링된 HTML을 React가 takeover(인수)하여, 이벤트 핸들러 및 상태 관리를 추가하는 과정
→ SSR된 정적인 HTML에 JavaScript코드를 추가하여 React가 HTML코드를 다시 활성화하는 과정

그렇다면 무조건 SSG가 SSR보다 좋은가? 꼭 그렇지만은 않다. 모든 것은 장단점이 있듯이 사용목적에 맞게 사용하면 된다.

SSG와 SSR의 비교

SSR을 써야하는 경우

SSR의 특징인 사용자의 요청마다 HTML을 반환하는 것을 생각하면 어떤 경우에 사용하면 좋을지 알 수 있다.

  • 사용자별 맞춤 페이지
    • 로그인한 사용자마다 다른 데이터를 보여줘야 하는 경우
    • 즉 사용자마다 다른 데이터를 보여줘야 할때는 SSR이 더 유리하다. SSR의 특징인 요청이 들어올 때 마다 HTML을 반환하기 때문에 사용자의 요청마다 다른 HTML을 보여줘야 할 것이다.
  • 자주 변경되는 데이터
    • 주식이나 스포츠 경기 점수와 같이 실시간으로 변경되는 데이터가 필요한 경우
    • SSG의 경우 빌드될 때 정적 HTML문서를 갖고 있으므로 실시간으로 변경되는 데이터를 다루기 힘들다. 하지만, SSR의 특징을 생각하면 실시간마다 사용자의 요청에 따라 HTML을 반환할 수 있다.
  • SEO가 중요한 동적 페이지
    • 검색엔진 최적화(SEO)를 고려해야 하지만, SSG로 미리 생성할 수 없는 경우
    • 뉴스 사이트나 실시간 댓글
    • 이 경우도 마찬가지로 실시간의 성질을 띄고 있다.

SSG를 써야하는 경우

  • 변경이 적은 컨텐츠
  • 빠른 로딩 속도가 필요한 페이지
  • 트래픽이 많은 사이트
    • SSR의 경우에는 서버 요청에 부담이 갈 수 있기 때문에 SSG와 같이 미리 생성된 파일을 제공하는 것이 트래픽이 많은 사이트에서 강점을 가질 수 있다.

업그레이드된 SSG : ISR방식의 등장

SSG는 변경이 적은 컨텐츠에 유리하다. 변경이 잦게 되면 캐싱을 하는 의미가 없기 때문이다. 이러한 단점을 보완하기 위해 ISR(Incremental Static Regeneration)방식이 도입되었다.

ISR방식

ISR증분 정적 재생성이라고도 합니다. 또한, SSG와 SSR의 장점을 조합한 방식입니다.

SSG처럼 정적 페이지를 미리 생성하지만, 특정 주기마다 새로운 데이터를 반영할 수 있습니다. 즉, 빌드를 다시 하지 않고도 정적 페이지를 일정 시간마다 자동으로 갱신할 수 있습니다.

정리하면 정적 페이지 + 특정 주기마다 자동 업데이트하는 하이브리드 방식

revalidate 속성

ISR이 작동할 수 있는 이유는 revalidate속성 때문입니다.

공식문서의 코드를 참고해보겠습니다. 여기서 다 볼 필요는 없고 중요한 것은 아래의 revalidate입니다.

function Blog({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}

// This function gets called at build time on server-side.
// It may be called again, on a serverless function, if
// the path has not been generated.
export async function getStaticPaths() {
  const res = await fetch('https://.../posts')
  const posts = await res.json()

  // Get the paths we want to pre-render based on posts
  const paths = posts.map((post) => ({
    params: { id: post.id },
  }))

  // We'll pre-render only these paths at build time.
  // { fallback: blocking } will server-render pages
  // on-demand if the path doesn't exist.
  return { paths, fallback: 'blocking' }
}

// This function gets called at build time on server-side.
// It may be called again, on a serverless function, if
// revalidation is enabled and a new request comes in
export async function getStaticProps() {
  const res = await fetch('https://.../posts')
  const posts = await res.json()

  return {
    props: {
      posts,
    },
    // Next.js will attempt to re-generate the page:
    // - When a request comes in
    // - At most once every 10 seconds
    revalidate: 10, // In seconds
  }
}

export default Blog

작동 과정

  1. 초기 빌드 타임 생성: 빌드 타임에 SSG 방식으로 페이지가 한 번 생성되며, 사용자가 이 페이지를 요청하면 생성된 정적 페이지가 즉시 반환됩니다.
  2. 갱신 주기 설정: revalidate로 설정한 시간이 지나기 전까지는 캐시된 기존 페이지가 반복적으로 제공됩니다.
  3. 갱신 주기 만료 시 첫 요청 처리: 설정된 시간이 지나고 첫 번째 요청이 들어오면 ISR 방식이 동작하여, Next.js는 우선 캐시된 페이지를 반환하고 서버는 백그라운드에서 새로운 페이지를 생성합니다.
  4. 새 페이지로 갱신: 이후의 요청부터는 최신 데이터가 반영된 새 버전의 페이지가 빠르게 제공됩니다.

위의 예시에는 revalidate 가 10초로 설정이 되어있습니다. 그렇다면 사용자가 접속하고 20초 후에 데이터를 요청했다면?

일단은 캐시된 페이지인 똑같은 페이지를 보여주고 백그라운드에서 새로운 페이지를 생성합니다.

다시 데이터의 요청이 있다면 새로운 페이지를 반환합니다.

주의사항

주의해야 할 것은 오해사항으로 revalidate 시간마다 새로 업데이트를 진행한다고 알 수도 있는데 그렇지 않다는 점에 유의합시다.


마무리

이렇게, 초기 SSR부터시작된 웹 페이지가 SPA를 기반한 CSR로 넘어가고, 다시 SSR로 돌아와 SSG와 ISR까지 발전하는 과정을 담았습니다.

그 과정에서 각 방식에 대한 비교를 정리해보았습니다. 궁금한 점이나 수정사항이 있다면 댓글로 알려주시기 바랍니다.

profile
원리를 이해하는 프론트엔드 개발자입니다

0개의 댓글