본문으로 건너뛰기

2D세카이 개발 비하인드 — 기술 스택과 선택한 이유

에흐날(Echnalb)·2026. 4. 5.·조회 2

Next.js·Fastify·Prisma·Babylon.js·Discord.js까지, 왜 이 조합을 골랐고 모노레포를 어떻게 운영하고 있는지 솔직하게 털어놓습니다.

왜 기술 스택 글을 쓰는가

"왜 이 프레임워크를 골랐느냐"는 취향 싸움이 되기 쉽습니다. 그래도 저희가 굳이 이 글을 쓰는 이유는, 2D세카이처럼 1인~소수 개발로 유지하는 프로젝트에서 기술 선택이 "써 봤던 것" 이상의 기준이 필요하다는 걸 체감했기 때문입니다. 이 글은 자랑이 아니라 "6개월 뒤의 내가 읽어도 납득할 만한 근거"를 남겨두기 위한 회고입니다.

모노레포 — pnpm workspace

구조

```
apps/
backend-api/ — Fastify REST + Socket.IO
client/ — Next.js 14 App Router
discord-bot/ — discord.js v14
packages/
shared/ — 타입·Zod 스키마·상수
prisma-client/ — Prisma 스키마·마이그레이션
```

선택 이유

npm 워크스페이스보다 `pnpm`이 설치 속도와 디스크 사용량에서 압도적으로 유리합니다. node_modules 하드링크 구조 덕분에 5개 서브 프로젝트를 깔아도 500MB 안쪽에서 끝납니다. 타입과 Zod 스키마를 `@gganada/shared`로 뽑아두면, 백엔드에서 정의한 요청 스키마를 클라이언트에서 그대로 재사용할 수 있어 API 스펙 드리프트가 거의 없습니다.

백엔드 — Fastify v5

Express가 여전히 기본 선택지이지만, 저희는 Fastify v5를 택했습니다. 이유는 세 가지입니다.

1. 스키마 기반 유효성 검사 내장 — JSON Schema(또는 Zod 어댑터)로 요청을 선언하면 런타임 검증과 OpenAPI 스펙이 거의 공짜로 나옵니다. 별도 `joi`/`express-validator`가 필요 없습니다.
2. 성능 차이 — 벤치마크에서 Express 대비 약 2~3배 처리량. 저희처럼 Socket.IO를 같이 얹는 구조에서는 초반 핸드셰이크 지연이 체감될 만큼 줄어듭니다.
3. 플러그인 캡슐화 — Fastify 플러그인은 스코프 단위로 감싸져 있어, "이 라우트 그룹에만 이 미들웨어" 구조가 자연스럽습니다. RBAC 미들웨어를 적용 구간별로 쉽게 갈라놓을 수 있습니다.

프론트 — Next.js 14 App Router + BFF 패턴

App Router를 고른 이유

Pages Router가 익숙하긴 하지만, 2D세카이는 서버 컴포넌트로 초기 HTML을 채우는 이점이 큰 서비스입니다. 블로그·공지·게임 상세 같은 페이지는 SEO와 링크 미리보기가 중요한데, App Router의 서버 컴포넌트가 자연스럽게 SSR을 만들어 줍니다.

BFF (Backend For Frontend) 패턴 — 브라우저 직접 호출 금지

브라우저는 절대 Fastify API를 직접 호출하지 않습니다. Next.js 서버 액션 또는 라우트 핸들러가 모든 요청의 중계자가 됩니다. 이렇게 한 이유는:

- API Key 보호 — 백엔드 인증 키가 브라우저에 노출되면 안 됩니다.
- 도메인 통일 — 브라우저 입장에서는 `2d-sekai.kr` 한 도메인만 존재해 CORS/쿠키 문제가 없습니다.
- 실패 처리 중앙화 — 네트워크 에러·5xx 재시도 로직을 BFF에서 한 번만 작성하면 됩니다.

3D — Babylon.js 9.x + Havok 물리

Three.js 대신 Babylon.js를 고른 이유

커뮤니티 규모만 보면 Three.js가 우세합니다. 그럼에도 저희는 Babylon.js를 골랐습니다.

- 통합 엔진 — 카메라·조명·포스트 프로세싱·GUI·물리 바인딩이 하나의 API로 제공됩니다. Three.js는 필요할 때마다 외부 라이브러리를 조합해야 합니다.
- 타입스크립트 친화 — Babylon.js는 처음부터 TS로 작성되어, 자동완성과 시그니처가 정확합니다.
- Havok 물리 — 공식 Havok 어댑터 덕분에 jenga-pull·flick-golf 같은 물리 게임을 빠르게 프로토타이핑할 수 있었습니다.

학습 곡선은 초반이 살짝 더 가파르지만, 한 번 구조를 잡고 나면 유지 보수 비용이 확실히 낮습니다.

데이터베이스 — Prisma v6 멀티 파일 스키마

모델이 84개, enum이 47개까지 늘어나자 단일 `schema.prisma`는 1,500줄이 넘어가 에디터에서 스크롤이 힘들어졌습니다. Prisma v6의 `prisma/schema/` 멀티 파일 기능으로 도메인별(`user.prisma`·`game.prisma`·`shop.prisma` 등) 27개 파일로 쪼개 두었습니다. PR 리뷰 시 어느 도메인이 바뀌는지 바로 보여서 훨씬 편합니다.

Discord 봇 — discord.js v14

슬래시 명령어 15개와 컨텍스트 메뉴 5개를 운영합니다. 단일 서버 전용이라 명령어 등록도 Guild 스코프로만 배포합니다 — Global 배포는 반영이 느리고, 저희에게는 장점이 없습니다. 봇에서 API로 보내는 요청은 모두 `X-API-Key: BOT_API_KEY` 헤더를 붙여 서명합니다.

Docker Blue-Green 배포

무중단 배포를 위해 Blue/Green 업스트림을 번갈아 가며 Nginx 업스트림을 바꿉니다. 헬스체크가 녹색일 때만 트래픽을 전환하고, 실패하면 자동 롤백합니다. 배포 스크립트 한 줄이면 되고, 5분 안에 끝납니다.

앞으로 바꾸고 싶은 부분

- Redis 이벤트 로그 영속화 — 현재는 메모리 이벤트가 많습니다. 일부를 Postgres 파티션 테이블로 이주할 예정입니다.
- 게임 엔진 서버 사이드 시뮬레이션 — 멀티 게임 일부는 아직 클라이언트 권위입니다. 치트 방지를 위해 권위를 서버로 이동 중입니다.
- Storybook — 컴포넌트 수가 700개를 넘어가면서 Storybook 없이는 시각 회귀가 어렵습니다.

FAQ

Q. Node.js 버전은 몇을 쓰나요?
A. Node 20 LTS입니다. Docker 이미지도 이 기준으로 동기화되어 있고, 로컬에는 Node가 없고 모든 빌드를 컨테이너 안에서 돌립니다.

Q. 오픈소스로 공개할 계획은 있나요?
A. 일부 유틸리티(게임 엔진 공용 훅, Canvas 렌더러 등)는 공개 후보지만, 플랫폼 전체 공개는 당장 계획에 없습니다. 운영 중인 단일 서버의 설정이 코드에 녹아 있어서, 분리·익명화 작업이 선행돼야 합니다.

Q. TypeScript 설정은 어떤 편인가요?
A. 전 프로젝트 strict mode, `noUncheckedIndexedAccess` 켜둔 상태입니다. 배열 접근이 귀찮지만, 프로덕션에서 `undefined` 런타임 에러를 거의 없애주는 투자라고 봅니다.