리액트를 배포할 수 있는 툴과 플랫폼은 매우 많습니다.
이때 어떤 것을 어떻게 골라야할지에 대해서 알아보겠습니다.
들어가기에 앞서서...
Single Page Application로 단일 페이지 모던 웹 애플리케이션을 뜻합니다.
다양한 웹 사이트를 하나의 페이지로 담습니다.사용자가 처음 웹 사이트에 접속하면 핵심 정적 리소스(HTML,CSS,JavaScript)를 다운로드합니다.사용자가 다른 페이지로 이동하면, 페이지에서 변경이 필요한 부분만 로딩하고 전체 페이지를 다시 로딩하지 않습니다.이때 클라언트 사이드 라우팅, 즉 클라이언트 측에서 JavaScript로 라우팅을 처리합니다.
반대되는 개념으로는 MPA(Multi Page Application)가 있습니다.페이지를 이동하면 서버에서 새로운 페이지를 새로 렌더링해서 전송해주는 방식입니다.페이지 변경 시, 매번 서버에 요청을 보내야 한다는 단점이 있습니다.이 단점을 보완하기 위해 SPA가 등장하게 된 것 입니다..!
사용자가 웹사이트에 요청을 보냄
(https://어쩌구저쩌구 사이트 방문)
서버는 HTML 문자열을 미리 생성 (renderToString() 또는 renderToPipeableStream() 사용)
(JS를 실행하지 않고도 페이지 구조와 텍스트를 완성)
브라우저는 빠르게 HTML을 렌더링
(단, 이 사이트는 상호작용 못함, JS 파일을 다운로드 하지 않았기 때문)
브라우저 JS 다운로드
사용자는 콘텐츠를 볼 수 있고, 조작이 기록될 수 있음
(SEO에 유리)
브라우저 JS 프레임워크 실행
(Hydration 시작, 서버가 생성한 HTML을 다시 읽고 내부 상태와 이벤트를 연결)
기록된 사용자 조작 실행, 페이지 상호작용 가능해짐
사용자가 웹사이트에 요청을 보냄
(https://어쩌구저쩌구 사이트 방문)
CDN이 빠르게 JS와 연결된 HTML 파일을 제공
(index.html 반환 (내용은 거의 비어있음))
브라우저는 HTML 다운로드 후 JS를 다운로드
(이때 동안, 사용자는 아무것도 볼 수 없음)
브라우저 JS 다운로드
서버는 API에 의해 요청받은 데이터를 응답
API로 부터 받은 데이터는 placeholder를 채우고 페이지는 상호작용이 가능해짐
간단하게 보자면,
CSR은 브라우저가 화면을 그리는 방식(JS 실행 후 렌더링), SSR은 서버가 미리 화면을 그려서 HTML로 보내는 방식 이라고 보면 됩니다.
이 둘의 차이를 왜 배포관점에서 알아야하죠?
CSR과 SSR은 렌더링 위치 (브라우저 vs 서버)가 다르기 때문에, 배포 시 필요한 인프라 구조,호스팅 방식,SEO 전략이 달라집니다.
CSR은 빌드 결과물을 CDN에 올리면 되지만, SSR은 서버 코드도 함께 배포해야 합니다.
빌드
개발용 코드를 브라우저가 이해할 수 있는 형태로 변환 및 최적화 하는 과정
프론트엔드에서는 webpack,vite와 같은 빌드툴이 있습니다.
JSX/TypeScript -> JavaScript로 변환
코드 압축, 번들링(파일 합치기)
npm run build
결과물은 /build 혹은 /dist 폴더에 생성됩니다.
브라우저는 이 폴더의 index.html,main.js,style.css 만 실행합니다.
CDN(Content Delivery Network)
웹 콘텐츠를 세계 곳곳에 있는 여러 서버에 분산하여 저장하는 분산 서버 네트워크 시스템 입니다.
오리진 서버
엣지 서버
DNS 서버

배포 구조
CSR
브라우저 -> CDN -> index.html + JS/CSS 반환
SSR
브라우저 -> 서버(Node.js, Lamda) -> HTML 생성(renderTostring) -> JS 로드 후 Hydration
이 외에도 알아야 할 것 들이 있지만 너무 길어질 것 같아 생략합니다..!
저는 가장 대표적인 배포 방식 3가지를 추렸습니다.
AWS amplify, Netlify, S3 + CDN
각각이 어떻게 다른지, 어디에 적합한지 확인해 보겠습니다.
풀스택 애플리케이션 개발을 지원하는 개발 플랫폼으로 완전 관리형 입니다.
정적 웹 사이트를 배포를 위한 플랫폼입니다.
S3 버킷을 정적 웹 사이트 호스팅용으로 사용하며 CloudFront(CDN)으로 전 세계 엣지 노드에서 콘텐츠를 제공합니다.
| 구분 | AWS Amplify | Netlify | S3 + CloudFront(CDN) |
|---|---|---|---|
| 특징 | AWS 기반 풀스택 호스팅 (CI/CD + 백엔드 통합) | 정적 사이트 + Jamstack 배포 플랫폼 | 정적 파일 저장 + CDN 조합 |
| 강점 (장점) | ✅ SSR/ISR 지원 (Next.js 등) ✅ 자동 빌드·배포 (Git 연동) ✅ 글로벌 CDN(CloudFront) ✅ AWS 서비스와 통합 용이 | ✅ 손쉬운 배포 (Git 연결만으로 즉시 배포) ✅ PR Preview(미리보기 URL) ✅ 내장 Functions / Edge 기능 ✅ 기본 CDN 내장 | ✅ 가장 저렴하고 단순 ✅ 완전한 제어 가능 (보안·정책 직접 설정) ✅ 매우 빠른 정적 콘텐츠 제공 ✅ 의존성 적음 |
| 약점 (단점) | ⚠️ 설정 복잡 (AWS 초심자에 어려움) ⚠️ CloudFront 직접 수정 불가 ⚠️ 비용 구조 복합 | ⚠️ SSR 한계 (정적 중심) ⚠️ 고급 로깅/통합 어려움 ⚠️ 트래픽 증가 시 비용 상승 | ⚠️ CI/CD, SSL, Redirect 수동 설정 ⚠️ SSR 불가 (정적만 가능) ⚠️ 운영 자동화 기능 부족 |
| 추천 사용 상황 (CSR) | SEO가 덜 중요한 SPA 앱 + AWS 리소스 통합이 필요한 경우 | 빠른 배포·개발 생산성 우선인 프로젝트 | 정적 사이트 / 랜딩 페이지 / 블로그 |
| 추천 사용 상황 (SSR) | SSR/ISR 필요 (Next.js, 개인화 콘텐츠 등) | 간단한 Edge SSR (한정적) SSR보단 CSR + Prerender 중심 | ❌ SSR 불가 (CloudFront + Lambda로만 흉내 가능) |
여러 시나리오에서 비교를 해보겠습니다.
| 시나리오 / 요구사항 | CSR 방식 적합 플랫폼 | SSR 방식 적합 플랫폼 | 이유 요약 |
|---|---|---|---|
| 마케팅 페이지 / 블로그 / 콘텐츠 중심 (정적인 페이지 많음) | S3 + CloudFront 또는 Netlify 또는 Amplify Static | 일부 SSR이 필요한 경우 Amplify SSR 또는 Netlify Edge 가능 | 정적 HTML 위주라 CSR → 프리렌더 또는 정적 생성 접근이 유리 |
| SPA앱 + 일부 SEO 중요 경로 (예: 유저 프로필, 상품 상세) | Amplify Static + 프리렌더 / Lambda@Edge 혼합 | Amplify SSR Hosting 또는 Next.js 배포 | 정적은 비용/운영 낮고, SEO 경로는 SSR 또는 Edge 처리 |
| 개인화 / 로그인 중심 플랫폼 (SNS,이커머스,금융/투자) | 가능하지만 SSR 쪽이 강세 | SSR 또는 ISR 기반 플랫폼 (Amplify SSR, 서버리스 SSR) | 사용자별 콘텐츠가 많고 초기 렌더링 속도/SEO 중요 |
| 짧은 개발 시간 / 빠른 배포 우선 | Netlify 또는 Amplify Static | Amplify SSR (자동 감지) | 개발자 경험이 우선일 때 Netlify나 Amplify가 설정 부담 낮음 |
| 예산/운영 최소화 | S3 + CloudFront (정적 위주) | SSR은 비용 증가하므로 제한적으로 | 서버리스 실행, 요청 수 등에 따라 추가 비용 발생 |
CSR 프로젝트를 Amplify를 이용해서 배포를 진행했습니다. 사실 크게 문제될 것은 없지만(정상적으로 작동하니까요..?ㅎㅎ) Amplify의 장점 중 하나인 서버리스/함수 부분을 챙기지 못한다는 것이 왠지 마음에 걸립니다.
이럴 경우에 해결책은 크게 2가지 방법으로 볼 수 있을 것 같습니다.
저는 이미 Amplify의 편리함에 익숙해진 사람이기 때문에 2번 방향으로 진행해보겠습니다.
저의 경우 CSR 프로젝트지만 SEO 가 필요한 상황입니다.
앞서 보았듯이 CSR 프로젝트는 SEO 가 되지 않는다는 단점이 있습니다!
그렇다면 CSR 에 프리렌더 되는 HTML을 내려준다면.. 그게 SSR이면서 SEO를 가능하게 할 수 있지 않을까? 라는 생각이 들었습니다.
페이지를 사전에 생성하여, 브라우저가 초기 로드 시 바로 콘텐츠를 표시할 수 있도록 하는 기술입니다.
즉, HTML을 미리 생성하여 클라이언트에 제공하는 방식을 의미합니다.
-> SEO 개선 가능!
대표적인 도구 중 하나인, react-snap의 사용방식을 살펴보겠습니다.
react-snap은 Puppeteer를 사용해 페이지를 크롤링하고 정적 HTML을 생성합니다.
사용방법
package.json을 변경합니다."scripts": {
"postbuild": "react-snap"
}
src/index.js를 변경합니다. import { hydrate, render } from "react-dom";
const rootElement = document.getElementById("root");
if (rootElement.hasChildNodes()) {
hydrate(<App />, rootElement);
} else {
render(<App />, rootElement);
}
끝! 이지만
굳이굳이 Amplify를 활용하는 방법도 알아보겠습니다.
Amplify + react-snap
우선 Amplify (나의 앱)/호스팅/빌드설정 탭에서 빌드 사양 YML 파일 관리가 가능합니다.
package.json 을 변경합니다.{
"scripts": {
"build": "react-scripts build",
"preview:static": "npx serve -s build -l 4173",
"postbuild": "concurrently -k \"npm:preview:static\" \"sleep 2 && npx react-snap\""
},
"reactSnap": {
"crawl": true,
"inlineCss": true,
"source": "http://localhost:4173",
"saveAs": "html",
"include": ["/", "/about", "/products"]
},
"devDependencies": {
"concurrently": "^8",
"react-snap": "^1",
"serve": "^14"
}
}
한 줄 한 줄 다시 살펴보겠습니다.
"preview:static": "npx serve -s build -l 4173",
react-snap 실행을 위한 임시 서버 구동 명령어
- serve 패키지를 사용해 build 폴더를 로컬에서 띄움
-1 4173 으로 포트 번호를 4173으로 지정http://localhost:4173를 크롤링해서 HTML을 생성 "postbuild": "concurrently -k \"npm:preview:static\" \"sleep 2 && npx react-snap\""
concurrently는 여러 명령어를 동시에 실행하는 유틸리티
-k는 한 프로세스가 종료되면 나머지도 모두 종료 시킴
위에서 만든 임시 서버를 실행
sleep 2 && npx react-snap 2초 기다린 후 react-snap이 크롤링 시작
결과적으로, react-snap이 서버에 접속해 페이지별 HTML을 미리 만들어 build 폴더에 저장
},
"reactSnap": {
"crawl": true,
"inlineCss": true,
"source": "http://localhost:4173",
"saveAs": "html",
"include": ["/", "/about", "/products"]
},
- react-snap 설정 부분
- `"crawl": true` react-snap이 HTML 내부의 `<a>` 링크를 자동 추적하여 추가 페이지를 렌더링합니다.
- `"inlineCss": true` CSS 를 HTML에 인라인으로 삽입하여 초기 렌더 속도를 개선합니다.
- `"source": "http://localhost:4173"` react-snap이 어떤 서버를 기준으로 크롤링 할지 결정
- `"saveAs": "html"` 결과물을 HTML 파일로 저장
- `include": [...]` 프리렌더 할 경로(라우트) 명시
2. `amplify.yml` 설정
```yaml
frontend:
phases:
preBuild:
commands:
- npm ci
build:
commands:
- npm run build # CRA 빌드 -> build/ 생성
- npm run postbuild # react-snap 프리렌더 실행
artifacts:
baseDirectory: build # CRA는 build/, (Vite면 dist/)
files:
- '**/*'
cache:
paths:
- node_modules/**/*
postbuild 를 이용해 react-snap 프리렌더를 실행한ㄱㅖ..
데이터나 URL이 API 응답에 따라 달라지는 페이지라면, 계속해서 재빌드 하지 않는 이상은 SEO가 불가능합니다.
왜냐?
react-snap은 빌드 시점에서만 작동합니다.
따라서 해당 시점에는, /product/1,product/2와 같은 동적 라우트 목록을 알 수가 없습니다.
가장 명쾌한 방법은 Next.js 와 같은 SSR/SSG 프레임워크로 마이그레이션 하는 것 이지만 아주 큰 공사가 됩니다.
그럼에도 CSR을 유지하면서 SEO를 확보하는 방식에 대해서 알아보겠습니다.
"특정 경로"만 Lambda@Edge에서 즉석으로 HTML을 만들어 변환할 수 있습니다.
예시
/product/123 같은 상품 상세 페이지를 SSR처럼 보이게 한다고 가정
const fetch = require("node-fetch");
exports.handler = async (event, context, callback) => {
const req = event.Records[0].cf.request;
const ua = req.headers['user-agent']?.[0]?.value || '';
const isBot = /Googlebot|Bingbot|Twitterbot|LinkedInBot/i.test(ua);
// 봇만 SSR 흉내 HTML 생성
if (!isBot) return callback(null, req);
// 예: 상품 정보 가져오기
const match = req.uri.match(/^\/product\/(\d+)/);
if (!match) return callback(null, req);
const productId = match[1];
// API에서 데이터 가져오기
const apiRes = await fetch(`https://api.myapp.com/products/${productId}`);
const product = await apiRes.json();
// SSR 흉내 HTML 생성
const html = `
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<title>${product.name} | MyShop</title>
<meta name="description" content="${product.summary}">
<meta property="og:image" content="${product.image}">
</head>
<body>
<h1>${product.name}</h1>
<p>${product.summary}</p>
<img src="${product.image}" alt="${product.name}" />
</body>
</html>
`;
callback(null, {
status: '200',
statusDescription: 'OK',
headers: {
'content-type': [{ key: 'Content-Type', value: 'text/html; charset=utf-8' }],
'cache-control': [{ key: 'Cache-Control', value: 'public, max-age=300' }]
},
body: html
});
};
위와 같이 하면, 동적 페이지에서도 SEO 가능한 HTML을 선택적으로 내려줄 수 있습니다.
주의할 점
Lambda@Edge는 원본 배포가 us-east-1 필요
- Lamda@Edge는 CloudFront의 확장 기능으로, "중앙 제어 리전"인 us-east-1에 등록 필요
SEO 안정성: 콘텐츠 동일성(Paritiy)로 "클로킹" 방지
- 검색봇과 일반 사용자에게 의도적으로 다른 내용을 주면 검색엔진 가이드 위반(클로킹)으로 패널티 위험이 있음

프레임워크를 선택할 땐...신중히....