여태 웹 애플리케이션 개발에서는 다양한 렌더링 기법을 사용하여 성능을 최적화하고 사용자 경험을 향상시키는 데 집중되고 있습니다. 오늘 게시글에서는 이러한 렌더링 기법들인 SSR(Server Side Rendering), SSG(Static Site Generation/쓱 아닙니다), CSR(Client Side Rendering)에 대해 이해해보고, 각 기법의 장단점과 간단한 예제를 살펴보려고 합니다. 먼저 전통적인 웹 동작 방식부터 살펴보겠습니다.
전통적인 웹 애플리케이션은 MPA(Multi-Page Application)방식으로 동작했습니다. 사용자가 탭이나 링크를 클릭할 때마다 서버로부터 새로운 HTML을 받아와서 페이지 전체를 렌더링하며, 한 페이지에서 다른 페이지로 이동할 때 전체 콘텐츠를 로드해야하기 때문에 많은 페이지가 연결된 애플리케이션에게는 느려지는 이슈는 필연적이었습니다. 이를 해결하기 위하여 2000년대 초반에 AJAX를 통하여 개선하려했지만 페이지 복잡성 증가와 같은 역효과를 되려 가져왔고, 약 10년 후에서야 MPA와 AJAX의 업그레이드 버전인 SPA 모델이 출시되었습니다. 모던 웹의 패러다임인 SPA(Single Page Application)은 말 그대로 하나의 페이지로 구성된 웹 애플리케이션이며, 서버로부터 페이지 전체를 불러오지 않고 현재 페이지를 동적으로 작성하여 브라우저에 렌더링하는 CSR(Client Side Rendering)방식으로 동작됩니다.
위 사진은 SPA와 MPA 동작 방식의 차이점을 한눈에 보여줘서 가져와봤음니다. SPA 방식에 대해 검색해보면 많은 분들이 'SPA의 핵심 가치는 사용자 경험(UX) 향상에 있으며, 애플리케이션 속도 향상도 기대할 수 있어 Mobile First 전략에 부합한다'고 나옵니다. 이 말에 대해 이해가 안되어 부연 설명을 해보자면, SPA는 다양한 디바이스와 해상도를 고려할 수 있고 반응형 디자인을 쉽게 구현할 수 있어, 모바일 환경에서도 일관된 사용자 경험을 제공해주고 모바일 앱과 동일한 백엔드 코드를 재사용할 수 있기 때문에 개발 및 유지보수가 용이합니다. 이 두 가지 요점을 통하여 모바일 퍼스트 전략을 구현하는 것이 장점이라는 의미로 보입니다.
반면, MPA 방식도 장점이 있습니다. MPA는 새로운 페이지를 요청할 때마다 서버에서 렌더링된 HTML, CSS, Javascript와 같은 정적 리소스가 다운로드되는 SSR(Server Side Rendering)방식으로 렌더링됩니다. 여러 페이지를 생성하여 많은 수의 키워드를 타겟팅할 수 있기 때문에 Google에서 검색을 통하여 접근할 수 있는 트래픽 양이 늘어나기 마련입니다. 페이지별 HTML 파일이 존재하여 페이지를 크롤링하기 때문에 검색 엔진이 작동하는 방식으로 SEO(Search ENgine Optimization: 검색 최적화)에 친화적입니다. 추가적으로 서버에서 미리 만들어진 HTML을 전송받기 때문에 초기 로딩 속도가 빠르다는 장점도 있습니다.
SSR (Server-Side Rendering)
장점
- SEO 친화적: 서버에서 페이지가 완전히 렌더링된 후 클라이언트에 전달되기 때문에 검색 엔진이 콘텐츠를 쉽게 크롤링하고 인덱싱할 수 있습니다.
- 빠른 초기 로딩 속도: 초기 페이지 로드 시 이미 렌더링된 HTML을 전달받기 때문에, 클라이언트 측에서 빠르게 화면에 표시됩니다.
단점
- 페이지 이동시 느린 속도: 페이지 이동시 전체 페이지를 다시 렌더링하기 때문에 속도에 영향을 받습니다.
- 개발 복잡성 증가: 클라이언트 측과 서버 측 모두에 프레임워크를 사용해야 하기 때문에 개발 시간이 더 소요됩니다.
CSR (Client-Side Rendering)
장점
- 서버 부하 감소(응답속도): 초기 요청에 필요한 HTML만 전달하고, 나머지 렌더링 작업은 클라이언트에서 수행하기 때문에 서버의 부하가 줄어듭니다.
- 동적 콘텐츠 처리 용이: 클라이언트 측에서 자바스크립트를 통해 동적 콘텐츠를 업데이트할 수 있기 떄문에 실시간 데이터 처리에 유리합니다.
- 개발 효율성: 일관된 개발 환경을 제공하여 클라이언트 측 로직을 한 곳에서 관리할 수 있습니다.
단점
- 초기 로드 타임 증가: 초기 로드 시 필요한 파일을 모두 로드 및 실행해야하기 때문에 초기 페이지 로딩시 사용자가 대기해야합니다.
- SEO(검색 엔진 최적화) 문제 : 검색엔진에 대한 크롤링이 되지 않아 색인이 되지 않는 문제가 발생합니다.
그렇다면 우리는 SPA의 CSR 방식과 MPA의 SSR 방식의 이점을 모두 취할 수 없을까? 모바일 친화적이고 빠른 속도 및 응답속도 사용자 경험을 향상시켜주는 CSR의 장점과 빠른 초기 구동 속도와 SEO 친화적인 SSR을 동시에 쓸 수 없을까? 그 이유를 Next.js 가 해소시켜줍니다.!
Next.js는 Vercel에서 만든 React Framework입니다. SSR, CSR, SSG를 모두 지원하는 유연한 렌더링 방식을 제공해주고, Server Component와 Client Component를 통하여 원하는 페이지의 SSR 방식 적용을 통한 SEO 최적화도 가능합니다. 당연히 SSR 방식을 통하여 초기 구동 속도도 빨라지고 CSR을 초기 로딩 후 페이지 전환과 데이터 갱신이 빠르게 이루어져, 사용자에게 매끄러운 경험을 제공해줍니다. Next.js는 코드 분할을 자동으로 처리하여, 필요한 부분만 로드하고 성능을 최적화합니다.
자동 코드 분할(Automatic Code Splitting)가 정확히 뭔데?
자동 코드 분할은 웹 애플리케이션 성능 최적화를 위하여 Next.js가 제공하는 중요한 기능 중 하나입니다. 도무지 무슨 말인지 몰라서 따로 정리해봅니다. 웹 애플리케이션이 커질수록 모든 코드를 한 순간에 로드하는 방식은 비효율적입니다. 전체 코드를 한 번에 다운로드하면 초기 로딩 시간이 길어져 사용자 경험이 나빠지겠죠? 따라서, 필요한 코드만 로드하여 초기 로딩 속도를 단축시키고, 이후 필요한 기능이 있을 때 해당 코드만 추가적으로 로드하는 것이 효율적입니다. 이를 위하여 Next.js는 코드 분할 작업을 자동으로 처리해준다고 합니다. 개발자가 별도로 설정하지 않아도, Next.js는 페이지별로 필요한 코드만 분할하여 로드해줍니다.
명확한 이해를 돕기 위해 예시를 들어보겠습니다. 쇼핑몰 사이트가 있다고 가정해봅시다. 쇼핑몰의 메인 페이지, 제품 페이지, 결제 페이지가 있을 때, 자동 코드 분할을 통해여 다음과 같은 이점이 있습니다.
- 메인 페이지 로딩: 사용자가 페이지를 방문할 때, Next.js는 메인 페이지에 필요한 코드만 로드합니다. 제품 페이지나 결제 페이지와
관련된 코드는 로드되지 않습니다.
- 제품 페이지 로딩: 사용자가 제품 페이지로 이동하면, Next.js는 이 때 제품 페이지에 필요한 코드만 추가로 로드합니다. 이미 로드된 메인 페이지 코드는 다시 로드되지 않습니다.
- 결제 페이지 로딩: 결제 페이지로 이동할 때, 결제 페이지에 필요한 코드만 로드됩니다. 이처럼 필요한 시점에 필요한 코드만 로드함으로써 전체적인 로딩 시간을 줄이고, 사용자 경험을 향상시킬 수 있습니다.
이로써 Next.js의 자동 코드 분할 기능은 애플리케이션의 성능 최적화에 큰 도움을 줍니다. 초기 로딩 시간 단축, 불필요한 코드 로드 방지, 페이지별 효율적인 리소스관리 등을 제공하여 개발자가 성능 좋은 웹 애플리케이션을 쉽게 만들 수 있게 도와줍니다.
Next.js에서의 CSR, SSR, SSG 예제를 살펴보기 앞서 Pre-Rendering과 Hydration 개념에 대해 정리하고 넘어가겠습니다. (살펴볼게 참 많죠?) Pre-Rendering(사전 렌더링)이란 서버 단에서 DOM 요소들 빌드하여 HTML 문서를 렌더링하는 것을 의미하며, 초기 페이지 로드 속도를 향상시키고, SEO를 개선하는 데 목적이 있습니다. 이미 앞에서 각 렌더링 방식에 대한 설명과 동일한 내용입니다.
Hydration은 이렇게 미리 렌더링된 HTML에 javascript를 결합하여 이벤트가 동작할 수 있게 만드는 과정을 말합니다. 클라이언트가 서버에서 받은 HTML을 React 애플리케이션으로 변환하는 과정으로 이해하시면 되겠습니다. Pre-Rendered HTML을 클라이언트에서 받은 후, React는 이벤트 핸들러 등 동적인 기능을 추가하여 전체적으로 인터렉티브하게 만듭니다.
1. SSR(Server-Side Rendering) : 각 요청마다 HTML을 서버에서 동적으로 생성합니다.
2. SSG(Static Site Generation) : 빌드 시점에 HTML을 (정적으로) 생성하고 저장하여, 각 요청 시마다 재사용합니다.
따라서, Next.js은 렌더링할 때 기본적으로 두 가지 주요 방식인 SSR과 SSG 방식으로 Pre-Rendering을 수행하며, 둘의 차이는 HTML을 생성하는 시기에 있습니다. 기본적으로 SSG가 SSR보다 높은 성능을 가지는 것으로 알려져 있으며, Next.js에서도 SSG를 권장하고 있습니다. 이제 각 CSR/SSR/SSG 동작에 대한 정리와 어느 상황에 써야하는지, 그리고 예제를 살펴보면서 글을 마무리하려고 합니다. 이 글은 작년 10월 26일에 발표된 Next.js 14 버전을 기준으로 합니다.
1. CSR (Client-Side Rendering)
모든 렌더링이 클라이언트에서 이루어지며, 사용자와의 인터렉션이 많은 페이지에 적합합니다. 예를 들어, 사용자가 자주 상호작용하는 대시보드, 비동기 데이터 로드가 필요한 애플리케이션, SEO가 중요하지 않은 페이지가 이에 해당됩니다.
Next.js 14에서 App Router 방식으로 CSR을 구현하기 위해서 최상단에 "use client"를 필수로 입력하면 해당 페이지는 CSR로 동작됩니다. 또한, React Hook을 사용할 수 있는 페이지는 CSR로 작성되어야 합니다.
"use client"
import { useEffect, useState } from 'react';
export default function Dashboard() {
const [data, setData] = useState<DataType>();
useEffect(() => {
fetch('/api/data')
.then((response) => response.json())
.then((data) => setData(data));
}, []);
if (!data) return <div>Loading...</div>;
return (
<div>
<h1>Dashboard</h1>
<p>{data.message}</p>
</div>
);
}
2. SSR (Server-Side Rendering)
유저가 페이지를 요청할 때마다 서버에서 HTML을 동적으로 생성하는 방식이며, SEO 최적화에 유리하기 때문에 SEO와 초기 데이터가 중요한 페이지에 적합합니다. 항상 최신 상태로 유지되어야하는 페이지, 차트 등 사용자 요청마다 동적으로 페이지를 생성해서 다른 상태로 업데이트시켜주어야하는 상황에 사용됩니다.
CSR 방식을 위해 React Hook인 useEffect를 사용했다면, SSR 방식을 위해서는 getServerSideProps를 통하여 데이터를 불러옵니다. Next.js가 Pre-Rendering 중 getServerSideProps 함수를 발견하면 컴포넌트 함수 호출 전에 getServerSideProps를 먼저 호출하여 컴포넌트에 전달해줍니다. 하지만 App Router에서는 SSR을 구현하기 위해서는 getServerSideProps 대신 async 함수와 fetch API를 사용하여 데이터를 서버 측에서 가져옵니다.
export async function generateMetadata({ params }) {
const { id } = params;
const post = await fetchPostData(id);
return { title: post.title };
}
export default async function BlogPost({ params }) {
const { id } = params;
const post = await fetchPostData(id);
return (
<div>
<h1>{post.title}</h1>
<p>{post.content}</p>
</div>
);
}
3. SSG(Static Site Generation)
빌드 시점에 HTML을 생성하여 정적 파일로 제공해주며, 재사용되기 때문에 매우 빠른 페이지 로드 속도를 이점으로 하는 방식입니다. 주로 업데이트가 자주 필요하지 않은 블로그 게시물, 마케팅 페이지에 적합합니다.
Next.js에서 SSG 방식으로 데이터를 받아오려면 getStaticProps가 필요합니다. 빌드 시에 한 번만 호출되며, static file로 빌드되어 재사용되도록 해줍니다. App Router에서는 generateStaticParams와 generateMetadata 함수를 사용하여 빌드 시 정적 파일 생성합니다.
export async function generateStaticParams() {
const posts = await fetchAllPostIds();
return posts.map((post) => ({ id: post.id.toString() }));
}
export async function generateMetadata({ params }) {
const { id } = params;
const post = await fetchPostData(id);
return { title: post.title };
}
export default async function BlogPost({ params }) {
const { id } = params;
const post = await fetchPostData(id);
return (
<div>
<h1>{post.title}</h1>
<p>{post.content}</p>
</div>
);
}
'Frontend' 카테고리의 다른 글
React vs Svelte (feat.Vite) (2) | 2024.01.14 |
---|---|
Styled-components ThemeProvider 활용(1) (0) | 2021.12.22 |