1 / 1

05: 서버-프론트엔드 조합 가이드 — 언제 뭘 쓰는가

예상 시간: 5분

05: 서버-프론트엔드 조합 가이드 — 언제 뭘 쓰는가

이 문서에서 배우는 것

  • Flask + React/Svelte (정적 빌드): CSR 조합
  • Express + React/Svelte (직접 SSR): 수동 배관 조합
  • Next.js (React): 프로덕션 표준 SSR
  • SvelteKit (Svelte): 프로덕션 표준 SSR
  • 각 조합의 장단점 표
  • 실무 선택 가이드: 상황별 최적 조합

PM 요청 (김도연)

김도연 PM: 준혁님, 01~04편을 통해 정적 파일 서빙, CSR/SSR 차이, Next.js가 왜 다른지, 언어 런타임이 SSR을 결정한다는 걸 배웠는데요. 이제 종합적으로 정리해주세요. 실제로 프로젝트를 시작할 때 어떤 조합을 선택해야 하나요?

이준혁 시니어: 좋아! 지금까지 배운 걸 전부 종합해서 실무 가이드를 만들어보자. 각 조합의 장단점과 언제 뭘 써야 하는지 명확하게 정리할게.


시니어 멘토링 (이준혁)

4가지 주요 조합

이준혁: 서버-프론트엔드 조합은 크게 4가지로 나뉘어.

┌─────────────────────────────────────────────────────────────┐
│  조합 1: Flask + React/Svelte (정적 빌드)    │  CSR        │
│  조합 2: Express + React/Svelte (직접 SSR)   │  수동 SSR   │
│  조합 3: Next.js (React 내장)                │  자동 SSR   │
│  조합 4: SvelteKit (Svelte 내장)             │  자동 SSR   │
└─────────────────────────────────────────────────────────────┘

하나씩 자세히 보자.


조합 1: Flask + React/Svelte (정적 빌드)

구조

┌──────────┐        ┌──────────────────────────┐
│  브라우저  │──────→│  Flask (Python)            │
│          │        │                            │
│          │←──────│  /api/*  → JSON 응답       │
│          │  HTML  │  /*      → dist/index.html │
│          │←──────│          → 정적 파일 서빙   │
└──────────┘        └──────────────────────────┘

코드

# app.py — Flask 서버
from flask import Flask, send_from_directory, jsonify
import os
 
app = Flask(__name__)
BUILD_DIR = os.path.join(os.path.dirname(__file__), 'frontend', 'dist')
 
# API
@app.route('/api/users')
def get_users():
    users = db.query('SELECT * FROM users')
    return jsonify(users)
 
# 정적 파일 서빙 (React/Svelte 빌드 결과물)
@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def serve(path):
    if path and os.path.exists(os.path.join(BUILD_DIR, path)):
        return send_from_directory(BUILD_DIR, path)
    return send_from_directory(BUILD_DIR, 'index.html')
// frontend/src/App.jsx — React (CSR)
import { useState, useEffect } from 'react';
 
export default function App() {
  const [users, setUsers] = useState([]);
 
  useEffect(() => {
    // 브라우저에서 API 호출
    fetch('/api/users')
      .then(res => res.json())
      .then(setUsers);
  }, []);
 
  return (
    <ul>
      {users.map(u => <li key={u.id}>{u.name}</li>)}
    </ul>
  );
}

특징

렌더링: CSR (브라우저가 화면 그림)
서버 언어: Python
프론트 빌드: npm run build → 정적 파일
서버 역할: 파일 전달 + API
SSR: 불가 (Python에서 JS 실행 불가)
SEO: 불리 (빈 HTML)

장단점

장점단점
Python 백엔드 활용 가능SSR 불가 (SEO 불리)
구조가 단순함 (서버 1개)초기 로딩 느림 (빈 HTML → JS 실행)
배포 간단프론트 빌드 필요 (npm run build)
기존 Flask 프로젝트에 추가 쉬움HMR은 Vite 개발 서버 따로 필요
프론트 프레임워크 자유롭게 선택-

조합 2: Express + React/Svelte (직접 SSR)

구조

┌──────────┐        ┌───────────────────────────────────┐
│  브라우저  │──────→│  Express (Node.js)                 │
│          │        │                                     │
│          │←──────│  1. renderToString(React컴포넌트)   │
│          │  HTML  │  2. 완성된 HTML 응답                │
│          │←──────│  3. JS 번들 서빙 (Hydration용)      │
│          │  JS   │  4. /api/* → JSON 응답              │
└──────────┘        └───────────────────────────────────┘

코드

// server.js — Express SSR
import express from 'express';
import React from 'react';
import { renderToString } from 'react-dom/server';
import HomePage from './components/HomePage.jsx';
 
const app = express();
app.use('/public', express.static('public'));
 
// SSR
app.get('/', (req, res) => {
  const html = renderToString(<HomePage />);
 
  res.send(`<!DOCTYPE html>
    <html>
    <body>
      <div id="root">${html}</div>
      <script src="/public/bundle.js"></script>
    </body>
    </html>`);
});
 
// API
app.get('/api/users', async (req, res) => {
  const users = await db.query('SELECT * FROM users');
  res.json(users);
});
 
app.listen(3000);
// build.js — 빌드 설정 (수동)
import esbuild from 'esbuild';
 
// 서버 번들
await esbuild.build({
  entryPoints: ['./server.js'],
  outfile: './dist/server.js',
  platform: 'node',
  bundle: true,
  jsx: 'automatic',
});
 
// 클라이언트 번들 (Hydration용)
await esbuild.build({
  entryPoints: ['./client/entry.jsx'],
  outfile: './public/bundle.js',
  platform: 'browser',
  bundle: true,
  jsx: 'automatic',
  minify: true,
});
// client/entry.jsx — Hydration
import { hydrateRoot } from 'react-dom/client';
import HomePage from '../components/HomePage.jsx';
 
hydrateRoot(document.getElementById('root'), <HomePage />);

특징

렌더링: SSR (서버가 HTML 생성) + Hydration
서버 언어: JavaScript (Node.js)
빌드: 서버 번들 + 클라이언트 번들 수동 설정
서버 역할: HTML 생성 + JS 서빙 + API
SSR: 가능 (Node.js가 JS 실행)
SEO: 유리 (완성된 HTML)

장단점

장점단점
SSR 완전 제어 가능배관 코드를 직접 작성해야 함
SSR 원리를 깊이 이해 가능새 페이지 추가 시 5단계 수동 작업
프레임워크 의존성 없음빌드 설정 직접 관리 (esbuild/webpack)
극한의 커스터마이징 가능HMR 직접 구현 필요
학습 목적으로 매우 좋음Code Splitting 직접 구현

조합 3: Next.js (React)

구조

┌──────────┐        ┌───────────────────────────────────┐
│  브라우저  │──────→│  Next.js (Node.js)                 │
│          │        │                                     │
│          │←──────│  자동 SSR / SSG / ISR               │
│          │  HTML  │  자동 Code Splitting                │
│          │←──────│  자동 Hydration                     │
│          │  JS   │  API Routes 내장                    │
│          │←──────│  Middleware 내장                     │
└──────────┘        └───────────────────────────────────┘

코드

// app/page.tsx — 서버 컴포넌트 (기본값)
// SSR 자동! 배관 코드 0줄!
export default async function HomePage() {
  // 서버에서 직접 DB 조회 — API 라우트 불필요
  const users = await db.query('SELECT * FROM users');
 
  return (
    <div>
      <h1>사용자 목록</h1>
      <ul>
        {users.map(u => <li key={u.id}>{u.name}</li>)}
      </ul>
    </div>
  );
}
// app/api/users/route.ts — API Route
export async function GET() {
  const users = await db.query('SELECT * FROM users');
  return Response.json(users);
}
// app/dashboard/page.tsx — 클라이언트 컴포넌트
'use client';
import { useState } from 'react';
 
export default function Dashboard() {
  const [count, setCount] = useState(0);
 
  return (
    <div>
      <h1>Dashboard</h1>
      <button onClick={() => setCount(c => c + 1)}>
        Count: {count}
      </button>
    </div>
  );
}

특징

렌더링: SSR / SSG / ISR / CSR (페이지별 선택)
서버 언어: JavaScript/TypeScript (Node.js)
빌드: next build (자동)
서버 역할: HTML 생성 + JS 서빙 + API + 미들웨어 + 모든 것
SSR: 자동 (기본값)
SEO: 최적 (자동 메타데이터, OG 태그, sitemap)

장단점

장점단점
SSR/SSG/ISR 자동 지원Node.js 서버 필요 (정적 호스팅 불가)
배관 코드 0줄프레임워크에 종속 (Next.js 규칙 따라야)
API Routes 내장내부 동작 블랙박스
자동 Code Splitting학습 곡선 (App Router, Server/Client Components)
Fast Refresh (HMR)빌드 시간 상대적으로 김
Image/Font 최적화 자동Python/Ruby 백엔드와 혼용 시 복잡
대규모 생태계 + 커뮤니티-

조합 4: SvelteKit (Svelte)

구조

┌──────────┐        ┌───────────────────────────────────┐
│  브라우저  │──────→│  SvelteKit (Node.js)               │
│          │        │                                     │
│          │←──────│  자동 SSR / SSG                     │
│          │  HTML  │  자동 Hydration                     │
│          │←──────│  API Routes (Server Routes) 내장    │
│          │  JS   │  더 작은 JS 번들 (컴파일러)         │
└──────────┘        └───────────────────────────────────┘

코드

<!-- src/routes/+page.svelte — 페이지 -->
<script>
  export let data;
</script>
 
<h1>사용자 목록</h1>
<ul>
  {#each data.users as user}
    <li>{user.name}</li>
  {/each}
</ul>
// src/routes/+page.server.ts — 서버 데이터 로딩
import type { PageServerLoad } from './$types';
 
export const load: PageServerLoad = async () => {
  const users = await db.query('SELECT * FROM users');
  return { users };
};
// src/routes/api/users/+server.ts — API Route
import { json } from '@sveltejs/kit';
 
export async function GET() {
  const users = await db.query('SELECT * FROM users');
  return json(users);
}

특징

렌더링: SSR / SSG / CSR (페이지별 선택)
서버 언어: JavaScript/TypeScript (Node.js)
빌드: vite build (자동)
서버 역할: HTML 생성 + JS 서빙 + API
SSR: 자동 (기본값)
SEO: 우수 (자동 메타데이터)
특이점: 컴파일러 기반 → 더 작은 JS 번들

장단점

장점단점
SSR/SSG 자동 지원Node.js 서버 필요
더 작은 JS 번들 (컴파일러)React보다 작은 생태계
직관적인 문법채용 시장이 React보다 작음
빠른 빌드 속도 (Vite 기반)대규모 프로젝트 레퍼런스 적음
Server Routes 내장서드파티 라이브러리 선택지 적음
코드 양이 적음 (보일러플레이트 적음)-

종합 비교표

이준혁: 4가지 조합을 한눈에 비교해보자.

항목Flask + 정적 빌드Express + 직접 SSRNext.jsSvelteKit
렌더링CSRSSR (수동)SSR/SSG/ISR (자동)SSR/SSG (자동)
서버 언어PythonJavaScriptJavaScriptJavaScript
SSR불가가능 (수동)자동자동
SEO불리유리최적우수
초기 로딩느림빠름빠름빠름
서버 부하낮음높음중~높음중~높음
배포 복잡도낮음높음중간중간
배관 코드최소많음없음없음
빌드 설정프론트만서버+클라이언트 수동자동자동
HMRVite 별도직접 구현자동자동
Code SplittingVite 자동직접 구현자동자동
APIFlask 직접Express 직접API RoutesServer Routes
학습 목적좋음매우 좋음중간중간
프로덕션 적합소규모특수 케이스표준표준
기존 Python 코드재사용 가능재작성 필요재작성 필요재작성 필요

실무 선택 가이드

김도연: 그래서 결국 뭘 써야 하나요?

이준혁: 상황별로 명확한 가이드를 줄게.

의사결정 트리

프로젝트를 새로 시작하는가?

├── Yes: SSR이 필요한가?
│   │
│   ├── Yes: React 생태계를 원하는가?
│   │   ├── Yes → Next.js ⭐
│   │   └── No  → SvelteKit ⭐
│   │
│   └── No: 어떤 프론트 프레임워크를 원하는가?
│       ├── React  → Vite + React (정적 빌드)
│       ├── Svelte → Vite + Svelte (정적 빌드)
│       └── Vue    → Vite + Vue (정적 빌드)
│       └── 서버? → 아무거나 (Flask, Express, Nginx, CDN)

└── No: 기존 백엔드가 있는가?

    ├── Flask/Django (Python):
    │   ├── SSR 불필요 → Flask + 정적 빌드 (01편 방식)
    │   ├── SSR 필요 + Flask 유지 → Flask + Next.js + Nginx
    │   └── SSR 필요 + Flask 제거 가능 → Next.js로 전환

    ├── Express (Node.js):
    │   ├── 학습 목적 → Express + 직접 SSR (조합 2)
    │   └── 프로덕션 → Next.js 또는 SvelteKit으로 전환

    └── 기타 (Rails, Spring, Go):
        ├── SSR 불필요 → 기존 서버 + 정적 빌드
        └── SSR 필요 → Next.js/SvelteKit 별도 + 리버스 프록시

시나리오별 추천

이준혁: 구체적인 시나리오로 보자.

시나리오 1: 개인 블로그

요구사항: SEO 중요, 콘텐츠 중심, 빠른 로딩
추천: Next.js (SSG/ISR)
 
이유:
- 블로그 글은 정적 생성(SSG)으로 빌드 시점에 HTML 생성
- ISR로 새 글 발행 시 자동 재생성
- SEO 자동 최적화 (메타태그, sitemap, OG 태그)

시나리오 2: 사내 관리자 대시보드

요구사항: 로그인 필수, SEO 불필요, 차트/테이블 많음
추천: Flask + React (정적 빌드) 또는 Vite + React 단독
 
이유:
- SSR 불필요 (검색엔진 노출 안 해도 됨)
- 기존 Python 백엔드 재활용 가능
- 구조가 단순하고 배포 쉬움

시나리오 3: E-commerce 사이트

요구사항: SEO 필수 (상품 검색), 빠른 로딩, 결제 시스템
추천: Next.js
 
이유:
- 상품 페이지: SSR/ISR (검색 노출 + 실시간 재고)
- 장바구니/결제: CSR (비공개, 인터랙티브)
- API Routes로 결제 로직 처리
- 페이지별로 렌더링 방식 선택 가능

시나리오 4: 실시간 협업 도구 (Figma, Notion 같은)

요구사항: 실시간 인터랙션, 웹소켓, 로그인 필수
추천: Vite + React + 별도 백엔드 (Express/Flask)
 
이유:
- SSR 불필요 (로그인 후 사용)
- 실시간 인터랙션이 핵심 (CSR이 적합)
- 웹소켓 서버는 별도로 관리하는 게 좋음

시나리오 5: 학습/교육 목적

요구사항: SSR 원리 이해, 직접 구현해보기
추천: Express + React (직접 SSR)
 
이유:
- renderToString, Hydration을 직접 구현
- 배관 코드를 직접 작성하면서 원리 이해
- Next.js의 "마법"이 실제로 무엇을 하는지 체득

시나리오 6: 기존 Flask 프로젝트에 프론트엔드 추가

요구사항: Python 백엔드 유지, 프론트 현대화
추천: Flask + React/Svelte (정적 빌드)
 
이유:
- 기존 코드 수정 최소화
- 프론트엔드만 React/Svelte로 전환
- SSR 불필요하면 가장 간단한 방법

2025년 기준 실무 현황

이준혁: 마지막으로 실무에서 실제로 뭘 많이 쓰는지 알려줄게.

┌─────────────────────────────────────────────────────────────┐
│  2025년 기준 서버-프론트엔드 조합 현황                        │
│                                                               │
│  🏆 가장 많이 사용:                                          │
│     Next.js — React 프로젝트의 사실상 표준                   │
│     대기업, 스타트업 모두 채택 (Vercel, Netflix, TikTok 등) │
│                                                               │
│  📈 빠르게 성장:                                              │
│     SvelteKit — Svelte 프로젝트의 표준                       │
│     작은 번들, 빠른 성능으로 인기 급상승                     │
│                                                               │
│  💼 여전히 건재:                                              │
│     Flask/Django + React (정적 빌드)                         │
│     Python 생태계가 강한 분야 (ML/AI, 데이터)               │
│                                                               │
│  📚 교육/학습용:                                              │
│     Express + React (직접 SSR)                               │
│     원리를 이해하기 위한 용도로 활용                         │
│                                                               │
└─────────────────────────────────────────────────────────────┘

이준혁: 핵심을 한 줄로:

SSR 필요 없으면:  아무 서버 + React/Svelte 정적 빌드
SSR 필요하면:     Next.js (React) 또는 SvelteKit (Svelte)

핵심 정리

4가지 조합 요약

1. Flask + 정적 빌드
   → CSR, 간단, SSR 불가, Python 활용
   → "SSR 필요 없고 Python 쓸 때"
 
2. Express + 직접 SSR
   → SSR 수동, 복잡, 원리 학습용
   → "SSR 원리를 배울 때"
 
3. Next.js
   → SSR 자동, 프로덕션 표준, React 생태계
   → "React로 프로덕션 서비스 만들 때"
 
4. SvelteKit
   → SSR 자동, 작은 번들, Svelte 생태계
   → "Svelte로 프로덕션 서비스 만들 때"

최종 의사결정 요약표

상황추천 조합
Python 백엔드 + SEO 불필요Flask + React/Svelte 정적 빌드
SSR 원리 학습Express + React 직접 SSR
React + SEO 필요Next.js
Svelte + SEO 필요SvelteKit
새 프로젝트 + 빠른 개발Next.js 또는 SvelteKit
사내 도구 / 대시보드Vite + React 또는 Svelte (CSR)

핵심 한 줄

"SSR 필요 없으면 Flask + 정적 빌드로 충분하고, SSR 필요하면 Next.js 또는 SvelteKit을 쓴다."


T003 시리즈 전체 요약

이준혁: 5편 전체를 관통하는 하나의 이야기로 정리할게.

01편: React/Svelte를 빌드하면 "파일"이 된다. Flask도 서빙 가능.

02편: 이 방식은 CSR. SSR은 서버가 HTML을 만드는 것.

03편: Next.js 빌드 결과는 "파일"이 아니라 "애플리케이션". Node.js 필요.

04편: SSR 가능 여부 = 언어 런타임. JS 컴포넌트 → JS 엔진(Node.js) 필요.

05편: SSR 필요 없으면 아무 서버 + 정적 빌드. 필요하면 Next.js/SvelteKit.

김도연: 결국 "빌드 결과물이 파일이냐 애플리케이션이냐"가 모든 것의 시작이었네요!

이준혁: 정확해! 그리고 그 차이를 만드는 건 SSR의 필요성이고, SSR이 가능한지를 결정하는 건 서버의 언어 런타임이야. 이 흐름을 이해하면 어떤 기술 조합이든 왜 그렇게 구성되는지 바로 보일 거야.


작성: 2026-02-09 버전: 1.0 예상 독서 시간: 15분