웹/React

CSR vs SSR (feat. Next.js)

한땀코딩 2021. 3. 7. 23:49

페이지는 누가 렌더 하는가?

리액트를 공부하다 보면 접하게 되는 용어 중 CSR(Client-Side Rendering)이 있습니다. 이름 그대로 클라이언트 측에서 렌더링을 하는 것인데, 이것이 정확히 무슨 의미일까요? 이와 비교되어 항상 등장하는 다른 용어로는 SSR(Server-side Rendering)이 있는데 둘은 어떻게 다를까요? 이를 설명하기 위해선 다양한 방법이 존재하겠지만, 이번 글에서는 간단하게 Next.js를 사용해보면서 Next.js가 제공하는 data-fetching 함수와 연결하여 설명해보려고 합니다.

Next.js란?

 

Next.js by Vercel - The React Framework

Production grade React applications that scale. The world’s leading companies use Next.js by Vercel to build static and dynamic websites and web applications.

nextjs.org

Next.js는 리액트의 프레임워크로, 리액트 기반으로 작성된 페이지를 SSR, 혹은 SSG 형태로 편리하게 제공할 수 있도록 해줍니다. 리액트만으로도 SSR을 구현할 수 있다고는 하지만 (필자는 아직 해본 적은 없습니다), 상당히 신경 쓸 부분이 많고 어렵기 때문에 Next.js를 이용해 빠르게 구현할 수 있습니다. 이 외에도 Next.js가 제공하는 기능은 많습니다만, 이번 포스팅에서는 이런 디테일은 보류하겠습니다. Next.js에 관심이 가시거나 사용하실 분들이라면 위의 링크를 통해 공식 사이트로 가시면 굉장히 잘 정리된 공식 문서를 확인하실 수 있습니다.

여기서 제가 정리해보고자 하는 질문은 이렇습니다.

그래서 SSR은 왜 해야 되고 뭐길래 Next.js를 쓰는가? 리액트랑 뭐가 다르길래?

CSR vs SSR

React app도 결국 특정 서버를 통해 배포하고, Next app도 마찬가지입니다. 둘이 배포된 사이트로 요청을 보내면 모두 html을 포함하여 필요한 소스를 보내주는 것도 같습니다. 그럼 무엇이 '클라이언트'고, 무엇이 '서버'인 걸까요?

 

결국 사용자가 보는 실질적인 '화면'을 어느 쪽에서 완성하는가의 문제입니다. CSR은 기본적인 뼈대와 자바스크립트 파일 등을 우선 모두 받아온 뒤, 소스 코드를 실행하면서 화면을 그려나갑니다. 만약 데이터를 가져와서 화면에 뿌려야 한다면, 우선 빈 골격을 만들고 api를 호출하여 필요한 값을 불러와 지정된 방식으로 화면에 채워 넣습니다. 즉, 소스코드를 받은 쪽인 브라우저(클라이언트)가 화면을 완성합니다.

 

반면 SSR은 클라이언트에서 요청이 들어올 때마다 서버에서 화면을 미리 완성해서 그 완성품을 유저에게 전달합니다. 화면에 필요한 데이터가 있다면 서버단에서 api를 호출하여 필요한 데이터를 미리 HTML에 넣어 완성된 코드를 생성합니다. 그 후, 이렇게 완성된 것을 브라우저로 전달하고, 브라우저는 별도로 api를 요청할 것 없이 그 자체로 완성된 것을 받아 그냥 화면에 그려주면 됩니다. 즉, 소스코드를 제공하는 쪽인 서버가 화면을 미리 완성하기 때문에 '서버'사이드 렌더링인 것입니다.

 

엄밀하게는 다양한 경우의 수가 있고, 두 가지를 섞어서 사용하는 경우도 얼마든지 있지만, 아주 쉽게 생각하면 화면에 필요한 데이터를 어디서 요청하고 화면에 넣어주는가에 따라 CSR과 SSR이 구분됩니다.

getServerSideProps

 

Basic Features: Data Fetching | Next.js

Next.js has 2 pre-rendering modes: Static Generation and Server-side rendering. Learn how they work here.

nextjs.org

Next.js에서 SSR을 활용하려면 getServerSideProps라는 함수를 이용하면 됩니다. 코드를 한번 보겠습니다.

function Page({ data }) {
  // 페이지 컴포넌트
}

// 요청마다 불리는 함수
export async function getServerSideProps() {
  // 외부 API에서 데이터 받아오기
  const res = await fetch(`https://.../data`)
  const data = await res.json()

  // Page 컴포넌트에게 props로 data 넘기기
  return { props: { data } }
}

export default Page

Next.js는 리액트와는 다르게, 페이지 기반으로 코드를 작성하게 됩니다. 컴포넌트는 자유롭게 생성하고 불러와서 리액트처럼 사용하실 수 있지만, 리액트처럼 라우팅을 직접 명시하기보다는, pages폴더 아래 각각의 파일로 페이지 컴포넌트를 생성합니다. 이때, 특정 페이지를 생성하기 위해 필요한 데이터가 있다면 getServerSideProps 함수를 호출하면 됩니다. 위의 코드의 흐름을 재정의하면 이렇습니다.

  • 특정 페이지를 렌더링 할 때 필요한 데이터를 api를 호출하여 받아옵니다.
  • 해당 데이터를 정제하여 반환하는 객체의 props 프로퍼티에 객체 형태로 만들어 넣어줍니다. getServerSideProps는 export 합니다.
  • 이렇게 작성해두면, 같은 소스 파일에서 작성하는 Page 컴포넌트에 Next.js가 자동으로 넘겨줍니다. 이 페이지 컴포넌트는 default로 export 하면 됩니다.
  • Page에선 data가 props로 넘어온다고 생각하고 기존 리액트 코드를 작성하듯 페이지를 완성시킵니다.

처음에는 getServerSideProps 함수와 Page 컴포넌트 함수의 위치가 제멋대로인 것처럼 보여 헷갈리지만, 결국 빌드할 때 Next.js가 자동으로 연결시켜주기 때문에 저 흐름만 이해하면 코드를 작성하는 것은 어렵지 않습니다. 이렇게 만들어두는 페이지는 이제 유저가 요청을 할 때마다 api를 호출하여 데이터를 새로이 받아오고 페이지를 완성시킨 뒤 유저에게 전달합니다.

SSR은 어떤 장점이 있는가.

그래서 우리는 왜 리액트로도 할 수 있는 걸 굳이 Next.js를 써가며 힘겹게 작성하는 걸까요? 간단한 프로젝트에선 그 차이를 느끼기 어렵겠지만 SSR은 CSR이 갖는 한계점을 몇 가지 해결해줍니다.

  • SEO 최적화가 가능합니다. CSR은 위에서도 설명했듯 url을 입력하는 순간, 최초로 받아오는 소스코드는 아주 기본적인 뼈대만 지니고 있습니다. 이런 경우, 검색 엔진의 입장에선 리액트로 만든 사이트는 텅 빈 것처럼 느끼게 됩니다. 구글의 경우 리액트 앱의 SEO도 제공한다고 합니다만, 아직 그렇지 못한 검색 엔진도 많습니다. 내가 만든 사이트가 좋은 콘텐츠를 가지고 있다고 검색 엔진이 느껴야만 사용자들에게 노출되기 쉽겠죠? 그런 관점에서 페이지가 완성되어 전달되는 SSR은 당연히 유리합니다.
  • 첫 페이지 로딩 속도가 빨라집니다. CSR은 code-splitting 같은 걸 이용하지 않는 이상 해당 사이트가 필요로 하는 모든 자바스크립트 소스파일을 받아옵니다. index page 접근이라면 그 페이지에 필요한 소스코드만 있어도 충분할 테지만, 유저의 동작에 따라 동적으로 매번 페이지를 그려야 하기 때문에 당장 사용하지 않을 모든 소스코드까지 미리 받아오게 됩니다. 이렇게 되면 사이트의 크기가 커질수록 최초 페이지 로딩이 느려질 수밖에 없습니다. 이와 다르게 SSR로 생성한 페이지들은 요청이 올 때마다 그때그때 생성하여 보내기 때문에 첫 페이지를 로딩하는 속도가 월등히 빠릅니다. 단, 반대로 생각해보면 한번 로딩이 끝나고 나면 CSR이 페이지 전환은 훨씬 부드럽고 빠르겠죠?

결론적으로 어떤 걸 사용하면 되나요?

SSR이 늘 정답인 것은 절대 아닙니다. 데이터가 요청마다 한 번만 처리되어도 충분하고, 사용자의 인터렉션이 많아 동적으로 화면을 매번 렌더 해줄 필요가 없다면 SSR은 좋은 선택입니다. 하지만 부드러운 렌더링이 필요하고 부분적인 업데이트가 잦다면 CSR이 사용자 경험면에서 유리한 면도 많습니다. 결국 프로젝트의 성격에 따라 필요한 것을 잘 선택하면 됩니다. 그리고 만약 사이트가 요청마다 새로이 데이터를 받을 필요도 없이 굉장히 정적인 경우가 많다면 SSG(Static Site Generation)도 사용할 수 있고, 이 또한 Next.js가 제공합니다. 이에 관련해서는 기회가 될 때 다른 포스팅에서 소개해보겠습니다.