728x90
반응형
1. 서버 사이드 렌더링이란
서버 사이드 렌더링(SSR)은 웹 페이지를 서버에서 HTML로 완전히 렌더링한 후 클라이언트에 전송하는 방식입니다. 초기 로딩 속도가 빠르고 SEO에 유리합니다.
1.1 CSR vs SSR
CSR (Client-Side Rendering):
서버 → 빈 HTML + JS → 클라이언트에서 렌더링
SSR (Server-Side Rendering):
서버에서 렌더링 → 완성된 HTML → 클라이언트 (하이드레이션)
1.2 SSR의 장단점
장점:
- SEO 최적화 (검색 엔진이 콘텐츠 크롤링 가능)
- 빠른 First Contentful Paint (FCP)
- 소셜 미디어 미리보기 지원
단점:
- 서버 부하 증가
- TTFB(Time To First Byte) 증가
- 복잡한 상태 관리
2. 기본 SSR 구현
2.1 Express + 템플릿 엔진
const express = require('express');
const app = express();
// EJS 템플릿 엔진 설정
app.set('view engine', 'ejs');
app.set('views', './views');
// 데이터 가져오기
async function fetchPosts() {
// 데이터베이스 또는 API에서 데이터 가져오기
return [
{ id: 1, title: '첫 번째 글', content: '내용...' },
{ id: 2, title: '두 번째 글', content: '내용...' }
];
}
// SSR 라우트
app.get('/', async (req, res) => {
const posts = await fetchPosts();
res.render('index', {
title: '블로그',
posts
});
});
app.listen(3000);
<!-- views/index.ejs -->
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<meta name="description" content="블로그 메인 페이지">
</head>
<body>
<h1><%= title %></h1>
<ul>
<% posts.forEach(post => { %>
<li>
<h2><%= post.title %></h2>
<p><%= post.content %></p>
</li>
<% }) %>
</ul>
</body>
</html>
3. React SSR
3.1 기본 React SSR
npm install react react-dom
// server.js
const express = require('express');
const React = require('react');
const ReactDOMServer = require('react-dom/server');
const App = require('./App').default;
const app = express();
app.use(express.static('public'));
app.get('*', (req, res) => {
const appHtml = ReactDOMServer.renderToString(
React.createElement(App)
);
const html = `
<!DOCTYPE html>
<html>
<head>
<title>React SSR</title>
</head>
<body>
<div id="root">${appHtml}</div>
<script src="/bundle.js"></script>
</body>
</html>
`;
res.send(html);
});
app.listen(3000);
// App.js
import React from 'react';
function App() {
return (
<div>
<h1>Hello SSR!</h1>
<p>서버에서 렌더링되었습니다.</p>
</div>
);
}
export default App;
3.2 데이터 페칭과 함께
// server.js
const express = require('express');
const React = require('react');
const ReactDOMServer = require('react-dom/server');
const app = express();
async function fetchData() {
// API 호출
return { users: [{ id: 1, name: '홍길동' }] };
}
app.get('/', async (req, res) => {
// 서버에서 데이터 페칭
const initialData = await fetchData();
const App = require('./App').default;
const appHtml = ReactDOMServer.renderToString(
React.createElement(App, { initialData })
);
const html = `
<!DOCTYPE html>
<html>
<head>
<title>React SSR</title>
</head>
<body>
<div id="root">${appHtml}</div>
<script>
window.__INITIAL_DATA__ = ${JSON.stringify(initialData)};
</script>
<script src="/bundle.js"></script>
</body>
</html>
`;
res.send(html);
});
app.listen(3000);
// client.js (하이드레이션)
import React from 'react';
import { hydrateRoot } from 'react-dom/client';
import App from './App';
const initialData = window.__INITIAL_DATA__;
hydrateRoot(
document.getElementById('root'),
<App initialData={initialData} />
);
4. Next.js
Next.js는 React SSR을 쉽게 구현할 수 있는 프레임워크입니다.
4.1 프로젝트 생성
npx create-next-app@latest my-app
cd my-app
npm run dev
4.2 페이지 라우팅
// pages/index.js
export default function Home({ posts }) {
return (
<div>
<h1>블로그</h1>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.content}</p>
</article>
))}
</div>
);
}
// 서버 사이드 데이터 페칭
export async function getServerSideProps() {
const res = await fetch('https://api.example.com/posts');
const posts = await res.json();
return {
props: { posts }
};
}
4.3 정적 생성 (SSG)
// pages/posts/[id].js
export default function Post({ post }) {
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
}
// 빌드 시 생성할 경로
export async function getStaticPaths() {
const res = await fetch('https://api.example.com/posts');
const posts = await res.json();
const paths = posts.map(post => ({
params: { id: post.id.toString() }
}));
return { paths, fallback: false };
}
// 빌드 시 데이터 페칭
export async function getStaticProps({ params }) {
const res = await fetch(`https://api.example.com/posts/${params.id}`);
const post = await res.json();
return {
props: { post },
revalidate: 60 // ISR: 60초마다 재생성
};
}
4.4 App Router (Next.js 13+)
// app/page.js
async function getPosts() {
const res = await fetch('https://api.example.com/posts', {
cache: 'no-store' // SSR
// cache: 'force-cache' // SSG
});
return res.json();
}
export default async function HomePage() {
const posts = await getPosts();
return (
<main>
<h1>블로그</h1>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
</article>
))}
</main>
);
}
// 메타데이터
export const metadata = {
title: '블로그',
description: '최신 글 목록'
};
5. Vue SSR (Nuxt.js)
5.1 Nuxt.js 프로젝트
npx nuxi init my-nuxt-app
cd my-nuxt-app
npm install
npm run dev
5.2 페이지와 데이터 페칭
<!-- pages/index.vue -->
<template>
<div>
<h1>블로그</h1>
<article v-for="post in posts" :key="post.id">
<h2>{{ post.title }}</h2>
<p>{{ post.content }}</p>
</article>
</div>
</template>
<script setup>
const { data: posts } = await useFetch('/api/posts')
</script>
5.3 API 라우트
// server/api/posts.js
export default defineEventHandler(async (event) => {
return [
{ id: 1, title: '첫 번째 글', content: '내용...' },
{ id: 2, title: '두 번째 글', content: '내용...' }
];
});
6. 스트리밍 SSR
React 18의 스트리밍 SSR로 점진적 렌더링이 가능합니다.
// server.js
const express = require('express');
const React = require('react');
const { renderToPipeableStream } = require('react-dom/server');
const App = require('./App').default;
const app = express();
app.get('/', (req, res) => {
res.setHeader('Content-Type', 'text/html');
const { pipe } = renderToPipeableStream(
<App />,
{
bootstrapScripts: ['/bundle.js'],
onShellReady() {
res.statusCode = 200;
pipe(res);
},
onError(error) {
console.error(error);
res.statusCode = 500;
res.send('Error');
}
}
);
});
app.listen(3000);
// App.js with Suspense
import React, { Suspense } from 'react';
const Comments = React.lazy(() => import('./Comments'));
function App() {
return (
<div>
<h1>Article</h1>
<p>Main content...</p>
<Suspense fallback={<div>Loading comments...</div>}>
<Comments />
</Suspense>
</div>
);
}
7. 캐싱 전략
7.1 페이지 캐싱
const express = require('express');
const NodeCache = require('node-cache');
const app = express();
const cache = new NodeCache({ stdTTL: 60 }); // 60초 캐시
app.get('/', async (req, res) => {
const cacheKey = 'homepage';
let html = cache.get(cacheKey);
if (!html) {
const data = await fetchData();
html = renderPage(data);
cache.set(cacheKey, html);
}
res.send(html);
});
7.2 CDN 캐싱
app.get('/posts/:id', async (req, res) => {
const post = await getPost(req.params.id);
const html = renderPost(post);
// CDN 캐싱 헤더
res.setHeader('Cache-Control', 's-maxage=60, stale-while-revalidate');
res.send(html);
});
8. SEO 최적화
// components/SEO.js
function SEO({ title, description, url, image }) {
return (
<>
<title>{title}</title>
<meta name="description" content={description} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:url" content={url} />
<meta property="og:image" content={image} />
<meta name="twitter:card" content="summary_large_image" />
<link rel="canonical" href={url} />
</>
);
}
// 사용
function PostPage({ post }) {
return (
<>
<SEO
title={post.title}
description={post.excerpt}
url={`https://example.com/posts/${post.id}`}
image={post.thumbnail}
/>
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
</>
);
}
9. 성능 최적화
9.1 컴포넌트 코드 스플리팅
// Next.js dynamic import
import dynamic from 'next/dynamic';
const HeavyComponent = dynamic(() => import('./HeavyComponent'), {
loading: () => <p>Loading...</p>,
ssr: false // 클라이언트에서만 렌더링
});
9.2 이미지 최적화
// Next.js Image
import Image from 'next/image';
function PostImage({ src, alt }) {
return (
<Image
src={src}
alt={alt}
width={800}
height={400}
priority={true}
placeholder="blur"
/>
);
}
결론
서버 사이드 렌더링은 SEO와 초기 로딩 성능이 중요한 웹 애플리케이션에 필수적입니다. React의 경우 Next.js, Vue의 경우 Nuxt.js를 사용하면 SSR을 쉽게 구현할 수 있습니다. 스트리밍 SSR로 점진적 렌더링을 하고, 적절한 캐싱 전략으로 서버 부하를 줄이는 것이 중요합니다. SSG(정적 생성)와 ISR(증분 정적 재생성)을 활용하면 더 나은 성능을 얻을 수 있습니다.
728x90
반응형
'Node.js' 카테고리의 다른 글
| Node.js의 Fastify 프레임워크 (1) | 2026.03.11 |
|---|---|
| Node.js의 NestJS 프레임워크 (0) | 2026.03.10 |
| Node.js의 Hapi.js 프레임워크 (0) | 2026.03.10 |
| Node.js의 Express.js 프레임워크 (0) | 2026.03.09 |
| Node.js의 HTTP 클라이언트 만들기 (0) | 2026.03.08 |