프론트와 백엔드를 완전히 분리하기로 한 이유
devdiary는 프론트엔드와 백엔드가 물리적으로 분리된 구조로 되어 있다. 프론트엔드는 Next.js(App Router) + TypeScript + Tailwind CSS로 만들어 Vercel에, 백엔드는 Express + TypeScript + Prisma ORM으로 만들어 Fly.io에 각각 올렸다.
두 프로젝트는 서로 다른 서버에서 독립적으로 돌아가고, 프론트엔드는 REST API로만 백엔드와 대화한다. 다만 리포지토리는 하나로 유지하면서, 그 안에서 프론트엔드와 백엔드 디렉토리를 나누는 모노레포 구조를 택했다.
카테고리 라우팅과 인증, 최대한 가볍게 가져가기
카테고리·서브카테고리 구조는 중첩된 동적 라우트로 구현해서, URL 경로만으로 특정 분류의 글 목록에 접근할 수 있게 했다. 각 페이지는 서버 컴포넌트로 작성해 요청이 들어올 때마다 백엔드에서 최신 데이터를 직접 받아오도록 했는데, 이렇게 하지 않으면 캐시 때문에 이미 삭제되거나 이름이 바뀐 카테고리가 화면에 그대로 남아있는 문제가 생긴다.
인증은 별도 상태관리 라이브러리 없이 처리했다. 로그인에 성공하면 토큰을 브라우저에 저장해두고, 인증이 필요한 요청마다 이 토큰을 함께 실어 보낸다. 로그인 여부도 클라이언트에서 토큰 존재 여부로 판단해서, 사이드바의 글쓰기·관리자 메뉴 노출을 조건부로 제어한다.
사이드바(카테고리 목록)와 관리자 페이지(카테고리 관리)는 서로 다른 컴포넌트 트리에 있어 상태를 직접 공유하지 않는다. 전역 상태 라이브러리를 새로 도입하는 대신, 카테고리가 변경될 때마다 브라우저 전역에 커스텀 이벤트를 발생시키고 사이드바가 이를 구독해 즉시 재조회하는 방식으로 가볍게 풀었다. 페이지 이동 없이도 사이드바가 실시간으로 갱신된다.
색상과 폰트는 CSS 커스텀 프로퍼티 기반 디자인 토큰으로 정의하고, Tailwind 설정 파일 대신 스타일시트 안에서 테마를 직접 관리하는 방식으로 전환했다.
게시글은 카테고리를 느슨하게 참조한다
게시글, 카테고리, 서브카테고리는 각각 별도 테이블로 분리했다. 게시글이 카테고리를 참조할 때는 외래키가 아니라 문자열 값(슬러그)으로 느슨하게 연결했다. 대신 카테고리 이름이나 슬러그가 바뀌면 그 카테고리를 참조하는 모든 게시글의 값도 함께 일괄 갱신해서 정합성을 맞춘다. 엄격한 외래키 제약을 걸지 않은 만큼, 삭제 시 생길 수 있는 문제는 애플리케이션 로직에서 직접 처리하는 쪽을 택했다.
카테고리나 서브카테고리를 삭제할 때 그 안에 게시글이 남아있으면, 게시글을 함께 삭제할지 다른 카테고리로 옮길지 둘 중 하나를 선택하도록 만들었다. 프론트엔드에서는 삭제를 시도하면 먼저 영향받는 게시글 수를 조회해 확인 화면에 보여주고, 사용자가 처리 방식을 선택해야만 실제 삭제가 진행된다.
관리자 비밀번호는 해시된 형태로만 저장하고, 로그인에 성공하면 일정 기간 유효한 인증 토큰을 발급한다. 글과 카테고리를 생성·수정·삭제하는 요청은 모두 이 토큰을 검증하도록 해서, 로그인하지 않은 사용자는 조회만 가능하고 쓰기 작업은 할 수 없게 막아뒀다.
Fly.io와 Vercel, 그리고 도메인 연결까지
백엔드는 컨테이너 이미지로 빌드해 Fly.io에 배포했다. SQLite 파일이 재배포나 재시작 이후에도 살아남도록 영구 디스크를 별도로 마운트하고, 데이터베이스 경로를 그쪽으로 지정했다. 배포가 일어날 때마다 마이그레이션이 자동으로 실행되도록 설정해서, "재배포하면 로컬 파일이 초기화되는" 흔한 문제 없이 SQLite를 그대로 프로덕션에 쓸 수 있었다. 트래픽이 없을 때는 서버가 자동으로 정지되도록 구성하고 할당 메모리도 줄여서, 개인 프로젝트 규모에 맞게 비용을 최소화했다.
프론트엔드는 GitHub 리포지토리와 연동해 코드를 푸시할 때마다 자동으로 재배포되도록 Vercel에 올렸다. 백엔드 API 주소는 코드에 하드코딩하지 않고 환경변수로 분리했다.
배포 초기에는 CORS 설정값이 실제 배포된 프론트엔드 주소와 일치하지 않아 요청이 차단되는 문제가 있었는데, 이 값을 실제 도메인으로 맞추면서 해결했다.
도메인은 가비아에서 구매해서 프론트엔드와 백엔드에 각각 연결했다. 루트 도메인은 프론트엔드로, 서브도메인은 백엔드로 향하도록 DNS 레코드를 나눠 등록했다. 등록 직후에는 설정이 전 세계에 반영되기까지 시간이 걸려 접속이 안 되는 구간이 있었고, 반영이 끝난 뒤에는 HTTPS 인증서를 재발급 요청해 안전한 접속을 마무리했다.
다음 목표
기본적인 CRUD와 배포 파이프라인은 갖춰졌으니, 다음은 이 순서로 갈 예정이다.
- -게시글 수정·삭제 기능(현재는 작성만 가능), About 페이지 콘텐츠의 관리자 편집 기능
- -반응형 레이아웃과 인터랙션 전반 개선
- -이미지 업로드, 수식 렌더링, 리치 텍스트·코드 하이라이팅 에디터 도입