오늘은 React와 서버 사이드 렌더링(SSR)에 대해 자세히 알아보겠습니다. 이 가이드는 SSR의 기본 개념부터 React에서의 실제 구현 방법, 그리고 고급 주제까지 다룰 예정입니다.
1. 서버 사이드 렌더링(SSR)이란?
서버 사이드 렌더링은 웹페이지의 초기 로드를 서버에서 처리하는 기술입니다. 전통적인 웹 개발 방식이었으나, 싱글 페이지 애플리케이션(SPA)의 등장으로 클라이언트 사이드 렌더링(CSR)이 주목받게 되었습니다. 하지만 최근 다시 SSR이 주목받고 있죠.
동작 방식:
- 사용자가 페이지를 요청합니다.
- 서버는 요청을 받아 필요한 데이터를 가져옵니다.
- 서버에서 React 컴포넌트를 렌더링하여 HTML을 생성합니다.
- 생성된 HTML을 클라이언트에 보냅니다.
- 클라이언트는 받은 HTML을 표시하고, React가 이어서 동작(hydration)합니다.
CSR vs SSR 비유:
- CSR: 레스토랑에서 주문 후 요리사가 눈앞에서 요리를 만드는 것
- SSR: 음식이 이미 준비된 상태로 테이블에 세팅되는 것
2. SSR의 장점과 단점
장점:
- 초기 로딩 속도 개선: 사용자는 완성된 콘텐츠를 빠르게 볼 수 있습니다.
- 검색 엔진 최적화(SEO): 검색 엔진 크롤러가 완전한 HTML 콘텐츠를 쉽게 읽을 수 있습니다.
- 소셜 미디어 공유 최적화: 페이지 미리보기가 정확하게 생성됩니다.
- 성능이 낮은 기기에서도 빠른 초기 렌더링: JavaScript 실행 전에도 콘텐츠를 볼 수 있습니다.
단점:
- 서버 부하 증가: 모든 요청마다 서버에서 렌더링하므로 서버 자원을 더 많이 사용합니다.
- 개발 복잡성: 서버와 클라이언트 양쪽을 고려해야 해서 개발이 더 복잡해질 수 있습니다.
- Time to Interactive (TTI) 지연: 초기 HTML은 빠르게 로드되지만, JavaScript가 로드되기 전까지 상호작용이 불가능할 수 있습니다.
3. React에서 SSR 구현하기
React에서 SSR을 구현하는 방법은 크게 두 가지입니다: 직접 구현하거나 Next.js 같은 프레임워크를 사용하는 것입니다.
3.1 직접 SSR 구현하기
직접 구현하는 방법은 복잡하지만, SSR의 동작 원리를 이해하는 데 도움이 됩니다.
1. 서버 설정 (Express 사용):
const express = require('express');
const React = require('react');
const ReactDOMServer = require('react-dom/server');
const App = require('./App');
const app = express();
app.get('/', (req, res) => {
const html = ReactDOMServer.renderToString(<App />);
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>My SSR React App</title>
</head>
<body>
<div id="root">${html}</div>
<script src="/bundle.js"></script>
</body>
</html>
`);
});
app.listen(3000, () => {
console.log('Server is running on http://localhost:3000');
});
2. 클라이언트 측 hydration:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.hydrate(<App />, document.getElementById('root'));
3.2 Next.js 사용하기
Next.js는 React 기반의 SSR 프레임워크로, SSR 구현을 매우 간단하게 만들어줍니다.
1.Next.js 프로젝트 생성:
npx create-next-app my-ssr-app
cd my-ssr-app
2.SSR 페이지 생성:
// pages/index.js
export default function Home({ serverData }) {
return <h1>서버에서 받은 데이터: {serverData}</h1>;
}
export async function getServerSideProps() {
// 서버 측 로직 실행
const res = await fetch('https://api.example.com/data');
const data = await res.json();
return { props: { serverData: data.message } };
}
3.동적 라우트에서의 SSR:
// pages/posts/[id].js
export default function Post({ post }) {
return (
<div>
<h1>{post.title}</h1>
<p>{post.content}</p>
</div>
);
}
export async function getServerSideProps({ params }) {
const res = await fetch(`https://api.example.com/posts/${params.id}`);
const post = await res.json();
return { props: { post } };
}
4. SSR vs CSR: 선택 기준
프로젝트의 특성에 따라 SSR과 CSR 중 적절한 방식을 선택해야 합니다.
SSR이 적합한 경우:
- SEO가 중요한 콘텐츠 중심의 웹사이트 (블로그, 뉴스 사이트 등)
- 초기 로딩 속도가 매우 중요한 경우
- 소셜 미디어 공유가 빈번한 사이트
CSR이 적합한 경우:
- 복잡한 사용자 인터랙션이 많은 웹 애플리케이션 (관리자 대시보드 등)
- 서버 리소스가 제한적인 경우
- SEO가 크게 중요하지 않은 경우
5. SSR의 성능 최적화
SSR을 사용할 때도 성능 최적화는 중요합니다:
1.코드 분할: 필요한 JavaScript만 로드하여 초기 로딩 시간을 줄입니다.
import dynamic from 'next/dynamic';
const DynamicComponent = dynamic(() => import('../components/DynamicComponent'));
2.캐싱: 서버 렌더링 결과를 캐시하여 서버 부하를 줄입니다.
export async function getServerSideProps({ req, res }) {
res.setHeader('Cache-Control', 's-maxage=10, stale-while-revalidate');
// ... 데이터 fetching 로직
}
3.스트리밍 SSR: 큰 페이지를 작은 청크로 나누어 점진적으로 전송합니다. (React 18 이상)
6. SSR의 미래: 새로운 동향
- 부분적 hydration: 페이지의 일부만 선택적으로 hydration하여 성능을 개선합니다.
- Islands Architecture: 정적 HTML 안에 동적 "섬"을 만들어 SSR과 CSR의 장점을 결합합니다.
- 서버 컴포넌트: React 팀에서 개발 중인 새로운 패러다임으로, 서버에서만 실행되는 컴포넌트를 만들 수 있습니다.
서버 사이드 렌더링은 React 애플리케이션의 성능과 SEO를 개선하는 강력한 도구입니다. Next.js와 같은 프레임워크의 등장으로 SSR 구현이 much 쉬워졌지만, 여전히 trade-off가 존재합니다.
SSR은 만능 해결책이 아니며, 프로젝트의 요구사항과 특성에 따라 적절히 사용해야 합니다. CSR, SSR, 그리고 둘의 하이브리드 접근 중에서 프로젝트에 가장 적합한 방식을 선택하는 것이 중요합니다.
React와 SSR을 잘 활용하여 사용자에게 더 나은 경험을 제공하는 웹 애플리케이션을 만들어보세요!