프로젝트 · 2026
Orbit
공부한 만큼 행성이 자라는, 학습 동기부여 웹앱
- Next.js
- FastAPI
- Three.js
개요
학습 주제를 하나의 행성으로 만들고, 공부 기록을 남길수록 그 행성이 조금씩 테라포밍되는 웹앱입니다. 프론트엔드 개발자로서 화면만이 아니라 백엔드·DB·인프라·배포까지 직접 구성하며 웹 서비스의 전체 구조를 손으로 이해하는 것을 목표로 만들었습니다. 직접 만든 디자인 시스템 @usetaehwan/ui를 우주 테마로 재사용해, 하나의 시스템이 서로 다른 결의 프로덕트를 어떻게 지탱하는지도 함께 검증했습니다.
작업 사례
사례 1
모바일에서만 로그인이 풀리는 문제
- 문제
- PC에서는 정상인데 iOS Safari에서 로그인 직후 세션이 유실됐습니다.
- 파악
- 프론트(Vercel)와 백엔드(Railway)가 다른 사이트라 세션 쿠키가 제3자 쿠키로 취급됐고, Safari의 추적 방지(ITP)가 이를 차단했습니다. SameSite=None으로 풀어도 근본 원인은 그대로였습니다.
- 해결
- Next.js rewrites로 프론트에 /api 프록시를 두어 브라우저 입장에서 same-origin 요청이 되게 했습니다. 쿠키는 SameSite=Lax로 되돌리고, CORS·OAuth 콜백·쿠키 속성을 환경변수로 빼 배포 환경마다 바꿀 수 있게 했습니다.
사례 2
진행도·스트릭을 저장하면 생기는 불일치
- 문제
- 학습 기록을 지우면 행성 진행도와 연속 기록(스트릭)이 실제 기록과 어긋날 수 있었습니다.
- 파악
- 진행도와 스트릭은 기록에서 계산되는 파생값인데, 이를 별도 컬럼에 저장하면 기록이 바뀔 때마다 동기화 코드가 필요하고 한 곳만 빠져도 불일치가 남습니다.
- 해결
- 저장하지 않고 조회 시점에 기록에서 계산하도록 services 계층에 두었습니다(행성 모델에 progress 컬럼 없음). 스트릭은 UTC로 저장된 시각을 KST 자정 기준으로 환산해 날짜 경계를 맞췄습니다.
사례 3
프론트엔드 개발자가 배포 파이프라인까지
- 문제
- 화면만 만들어 본 상태에서 백엔드·DB·에셋·인증을 각각 어디에 두고 어떻게 이어야 하는지가 비어 있었습니다.
- 파악
- 한 서비스에 몰아넣으면 쉽지만 무료 플랜 범위를 넘고, 나누면 크로스 도메인 문제(위 쿠키 이슈)가 따라옵니다. 나누되 경계 문제를 코드로 흡수하는 쪽을 택했습니다.
- 해결
- Vercel(프론트)·Railway(FastAPI)·Neon(PostgreSQL)·Cloudinary(이미지)로 분리 배포하고, GitHub Actions CI에서 ruff + pytest 65개 / tsc + eslint + Vitest 102개를 돌립니다. Playwright e2e 29개로 로그인·기록 흐름을 회귀 검증하고, 처음 SVG로 그렸던 행성은 React Three Fiber 3D로 교체했습니다.
핵심 기능
우주 테마로 재사용한 디자인 시스템
@usetaehwan/ui 패키지를 설치해, 토큰만 우주 테마로 덮어 재사용했습니다. 같은 프리미티브가 전혀 다른 무드의 프로덕트를 지탱하며 시스템의 이식성을 검증했습니다.
3D 행성 테라포밍
React Three Fiber로 행성을 실시간 렌더링하고, 학습 진행도에 따라 표면이 단계적으로 테라포밍되도록 구현했습니다. 기록이라는 추상적 행위에 눈에 보이는 성장의 피드백을 붙였습니다.
계층형 백엔드 + 테스트
FastAPI 백엔드를 api → services → models 계층으로 분리해 관심사를 나누고, PostgreSQL과 연결했습니다. 핵심 로직에 테스트를 붙여 프론트 밖의 안정성도 직접 책임졌습니다.
인증 · 배포
구글 OAuth와 크로스 도메인 쿠키 인증을 구성하고, 로컬 DB로 시작한 데이터를 클라우드(Neon)로 이전했습니다. Vercel·Railway·Cloudinary로 프론트·백엔드·에셋을 나눠 배포하며 풀스택 파이프라인을 완성했습니다.
스택
- Next.js
- React
- TypeScript
- Three.js (R3F)
- FastAPI
- PostgreSQL
- Google OAuth
- Vercel
- Railway
- Neon
- Cloudinary