지난 글에서 프레임워크를 고르기 전에 "API를 분리할지, 서버 렌더링이 필요한지"부터 정해야 한다고 적었다. 이 글은 그 앞 단계, 즉 서비스 전체를 어떤 구조로 나눌지에 대한 정리다. 새 프로젝트를 시작할 때마다 같은 고민을 반복해서, 판단 기준을 표 하나로 모아 두려고 쓴다.

세 가지 구조

구조 구성 요청 흐름
CSR + API Vite React SPA + 별도 API 서버(NestJS 등) 브라우저 → CDN(정적 파일), 브라우저 → API 서버 → DB
SSR 풀스택형 Next.js/Nuxt 하나에 페이지 + API + DB 접근 브라우저 → Next/Nuxt → DB
SSR 분리형 Next.js/Nuxt(렌더링 + BFF) + 별도 API 서버 브라우저 → Next/Nuxt → API 서버 → DB, 모바일 앱 → API 서버

실무에서는 이름이 제각각이라 대화가 꼬이기 쉽다. 풀스택형은 흔히 모놀리식(Monolith) 또는 통합형(coupled)이라 부르고, 분리형은 프론트-백 분리(Decoupled), API-first라고 부른다. 분리형에서 프론트 서버가 API를 중계하면 BFF(Backend For Frontend) 패턴, 하나로 배포하되 내부를 도메인별로 나누면 모듈러 모놀리식이다.

풀스택형에서 API를 만드는 방법도 프레임워크마다 이름이 다르다.

기능 Next.js Nuxt
내장 API 엔드포인트 Route Handlers (app/api/**/route.ts) Server Routes (server/api/**), 엔진은 Nitro
함수 호출형(RPC) Server Actions (React 용어로 Server Functions) 별도 개념 없음

측면별 비교

측면 CSR + API SSR 풀스택형 SSR 분리형
SEO·링크 미리보기 약함(빈 HTML) 강함 강함
첫 화면(FCP/LCP) 느림: JS 다운로드 → 실행 → API 호출 빠름 빠름(백엔드 왕복 1회 추가)
이후 화면 전환 빠름 빠름 빠름
SSG·ISR 프리렌더 플러그인으로 일부 기본 지원 빌드 시 API 호출로 지원
호스팅 정적 호스팅 + API 서버 Node 서버 1개 Node 서버 + API 서버
인프라 복잡도 낮음~중간 가장 낮음 가장 높음
단일 요청 성능 해당 없음(클라이언트 렌더) 유리: 함수 호출 + DB 직접 불리: 서버 간 HTTP + 직렬화
인증 토큰을 브라우저에 보관 서버 세션·쿠키로 단순 쿠키 ↔ 토큰 중계 필요
모바일 앱과 API 공유 가능 불편 가능
초기 개발 속도 빠름 가장 빠름 느림
타입 공유 OpenAPI 생성 등 별도 작업 자동 별도 작업
SSR 특유의 버그 없음 hydration 불일치, 요청 간 상태 누수 동일 + 서버 간 인증 이슈
프레임워크 종속 낮음 높음(비즈니스 로직까지) 중간(화면만)

흔한 오해: "분리형이 성능도 더 좋다"

단일 요청만 놓고 보면 오히려 풀스택형이 빠르다. 같은 프로세스 안에서 함수를 호출하고 DB에 바로 붙기 때문에 네트워크 왕복과 JSON 직렬화가 한 번 줄어든다. 분리형의 강점은 성능이 아니라 구조적 확장성, 즉 클라이언트·팀·기능이 늘어날 때 버티는 힘이다.

트래픽 확장도 마찬가지다. 풀스택형도 인스턴스를 늘리는 수평 확장으로 충분히 버틸 수 있고, 실제 병목은 대개 DB에서 먼저 온다. "나중에 트래픽이 많아지면"을 이유로 처음부터 분리형을 고르는 것은 비용 대비 근거가 약하다.

구조별로 잘 맞는 서비스

CSR + API는 단순하고 저렴하다. 정적 파일은 CDN에 올리면 끝이고, SSR 관련 버그가 없으며, 모바일 앱과 같은 API를 쓸 수 있다. 대신 SEO와 첫 화면이 약하다. 로그인 후에만 쓰는 서비스, 예를 들어 관리자 화면, 사내 인트라넷, 대시보드에 잘 맞는다. 검색 엔진이 볼 페이지가 애초에 없기 때문이다.

SSR 풀스택형은 SEO, 속도, 개발 속도를 한 번에 얻고 서버 하나로 운영한다. 앱이 생기거나 팀이 프론트·백으로 나뉘는 순간 애매해지고, 비즈니스 로직이 프레임워크에 묶인다. 웹 중심의 1~3인 프로젝트, 블로그·콘텐츠 사이트·MVP에 가장 잘 맞는다.

SSR 분리형은 SEO와 확장성, 앱과의 API 공유를 모두 얻지만 운영 복잡도와 비용이 가장 높다. 웹과 앱이 모두 있고 SEO도 중요한 서비스, 커머스나 커뮤니티 같은 곳이 대상이다.

판단 순서

질문 두 개면 대부분 정리된다.

  1. 검색 노출이나 링크 미리보기가 중요한가? 아니라면 CSR + API.
  2. 모바일 앱 같은 다른 클라이언트가 같은 API를 쓰는가? 아니라면 SSR 풀스택형, 그렇다면 SSR 분리형.

DailBit은 1번에 "예", 2번에 "아니오"라서 풀스택형 쪽이다. 다만 로그인·DB가 없고 글이 배포할 때만 바뀌므로, 서버조차 없는 극단적인 형태인 정적 Export(SSG)를 선택했다. 반대로 OTO나 토크잡이처럼 Vite SPA로 만든 서비스는 원래 1번에서 CSR로 갔다가, 공개 페이지의 링크 미리보기가 필요해져 빌드 시 프리렌더를 덧붙였다. 구조는 한 번 정하면 끝이 아니라 요구사항이 바뀔 때 다시 이 질문으로 돌아오게 된다.

BFF 한 줄 정리

[직접 호출]  Browser ──(토큰 헤더)──▶ API 서버 (외부 노출, CORS 필요)
[BFF]       Browser ──(httpOnly 쿠키)──▶ Next/Nuxt 서버 ──(토큰 헤더, 내부망)──▶ API 서버

분리형에서 BFF를 두면 얻는 것들이다.

  • 토큰을 httpOnly 쿠키에 두어 XSS로 토큰을 읽어 가는 위험을 줄인다.
  • API 서버는 내부망에만 두고 외부에 노출하지 않는다.
  • 브라우저 입장에서는 같은 도메인이라 CORS 설정이 필요 없다.
  • 여러 API 응답을 조합·가공해 화면에 맞는 형태로 내려준다. 이름 그대로 Backend For Frontend다.
  • SSR 시에도 같은 쿠키로 로그인 사용자의 데이터를 렌더링할 수 있다.

모바일 앱은 BFF를 거칠 이유가 없으니 API 서버를 직접 호출하면 된다.

커뮤니티의 추천 경향

집단 경향
인디·스타트업 초기·해외 커뮤니티 풀스택으로 시작(Monolith First). 도메인을 이해한 뒤 필요할 때 분리
국내 회사·엔터프라이즈 분리형이 기본. 앱이 거의 항상 있고 조직이 프론트·백으로 나뉨
최근 흐름 모듈러 모놀리식 재조명. MSA 복잡도에 대한 반작용

어느 쪽이 옳다기보다 조직 구조와 클라이언트 수가 결과를 결정한다고 보는 편이 정확하다.

실무에서는 섞어 쓴다

한 서비스가 한 가지 구조만 쓰는 경우는 오히려 드물다.

  • 한 프레임워크 안에서 경로별로 모드를 나눈다. 랜딩은 SSG, 상품 상세는 ISR, 마이페이지·관리자는 CSR. Nuxt는 routeRules, Next.js는 페이지별 렌더링·캐시 설정으로 표현한다.
  • 도메인으로 나눈다. www는 공개 SSR, app이나 admin은 로그인 영역 CSR SPA, 그리고 둘이 같은 API 서버를 쓴다.

풀스택형으로 시작하더라도 비즈니스 로직을 라우트 파일에 직접 쓰지 말고 lib/나 services/ 같은 서비스 계층으로 분리해 두는 것을 권한다. 나중에 별도 API 서버로 옮길 때 그 계층을 거의 그대로 가져갈 수 있다.

정리

질문은 "어느 구조가 더 좋은가"가 아니라 **"이 조건이면 이 복잡도를 낼 가치가 있는가"**다. SEO가 필요 없는 로그인 영역까지 SSR로 바꾸면 얻는 것 없이 운영 비용만 늘고, 앱이 없는데 API 서버를 따로 띄우면 서버 간 인증과 배포 순서라는 숙제만 생긴다. 지금의 요구사항을 가장 단순하게 만족하는 구조로 시작하고, 클라이언트나 팀이 늘어날 때 분리하는 것이 실패가 적었다.

참고 자료