은샘침례교회
교회 홈페이지와 API 서버 — 실제 운영 중
배경
충청남도 예산군 은샘교회의 홈페이지입니다. 교인은 설교·주보·소식을 보고, 성경 통독 챌린지와 차량 탑승 신청을 할 수 있습니다. 관리자는 콘텐츠와 회원, 역할·권한을 화면에서 직접 관리합니다.
처음부터 다른 교회에도 다시 쓸 수 있는 템플릿으로 설계했고, 새 교회는 .env만 채워 배포하는 "코드 수정 0"을 원칙으로 삼았습니다. 2026년 6월부터 백엔드와 프론트엔드를 혼자 만들었습니다.
맡은 역할
- 백엔드 설계·구현 — Spring Boot 4·Java 21·PostgreSQL·Redis, JWT 인증과 RBAC
- 프론트엔드 구현 — Next.js 16 App Router·TypeScript, 공개·회원·관리자 화면
- 디자인 시스템 — 디자인 토큰과 공통 컴포넌트, 고령 교인을 위한 큰 글자·UI
- 배포·운영 — 백엔드 OCI VM, 프론트엔드 Vercel, GitHub Actions CI/CD
- 문서화 — 설계 명세, 새 교회 배포 가이드, AI 에이전트용
AGENTS.md
과정
기능을 이슈 단위로 쪼개고, 설계 문서부터 썼습니다. 백엔드는 기반 4개와 도메인 13개로 나눴습니다. 기능마다 설계, 구현 계획, 구현, 코드리뷰 반영 순서로 PR을 만들어 6월 4일부터 7일까지 올렸습니다.
연혁 페이지는 설계만 네 번 했습니다. 풀스크린 히어로를 넣었다가 "모든 시대를 똑같이 다룬다"는 기준으로 걷어 냈습니다. 마지막에는 참고 사이트를 직접 스크롤하며 관찰해 풀폭 그리드로 바꾸고, 스크롤 엔진을 지워 코드를 줄였습니다.
실제 서버에 올리자 로컬에서는 안 보이던 문제가 나왔습니다. 백엔드가 멈추면 홈 전체가 에러 페이지가 되어, 소식 영역만 실패하도록 격리했습니다. 상세 페이지가 전부 500을 낸 날은 jsdom이 이전 Node 런타임에서 돌지 않는 것이 원인이라 Node 22로 고정했습니다.
기본 기능을 갖춘 뒤에도 기능을 이어서 붙였습니다. 성경 66권 1,189장 구조로 읽음 기록을 계산하는 통독 챌린지, 위치 좌표를 첨부하는 차량 탑승 신청, PWA, 검색엔진 최적화를 넣었습니다.
기술적 결정
멀티테넌시 대신 교회마다 별도 인스턴스
- 상황여러 교회에 다시 쓸 수 있는 백엔드가 목표였습니다.
- 결정테넌트 컬럼 없이 교회마다 DB와 배포 인스턴스를 따로 두어, 데이터 격리를 인프라에 맡겼습니다.
- 결과새 교회 배포는 코드 복사,
.env작성, 배포 세 단계이고, 이 절차를 문서로 남겼습니다.
직분과 권한을 분리한 RBAC
- 상황교회의 목사·장로·교사 같은 직분이 곧 사이트 권한은 아닙니다.
- 결정직분과 권한을 별개 축으로 두고, 인가는 항상
SERMON_WRITE같은 권한 단위로 검사합니다. - 문제처음에는 관리자가 다른 관리자를 임명하고 강등할 수 있었습니다.
- 결과역할 부여·회수는 자기보다 엄격히 낮은 역할만 가능하게 바꿨습니다.
등록·수정 폼은 모달 대신 전용 페이지로
- 상황공지·설교는 전용 페이지에서, 일정·갤러리·주보는 모달에서 등록·수정했습니다.
- 문제모바일에서 태그 선택창이 모달 뒤에 깔리는 등 모달 문제가 반복됐습니다.
- 결정모든 도메인을 전용 페이지 폼으로 통일하고, 삭제 확인 같은 작은 대화상자만 모달로 남겼습니다.
- 대안모바일에서만 모달을 전체 화면으로 키우는 안은 비용은 가장 적지만, 같은 계열의 문제가 남아 기각했습니다.
결과
www.eunsaem.com도메인으로 배포했습니다.- 설교·공지·일정·주보·갤러리, 성경 통독 챌린지, 차량 탑승 신청, 관리자 기능, PWA 설치를 지원합니다.
- 사이트맵·RSS·구조화 데이터를 갖추고 네이버 사이트 소유 확인을 적용했습니다.
- 부서 사진을 WebP로 변환·리사이즈해 46MB에서 5.5MB로 줄였습니다.
- 백엔드는 PR마다 실제 PostgreSQL로 전체 테스트를 돌리고,
deploy브랜치에 올리면 자동으로 배포됩니다.
배운 점
- 증상을 하나씩 막기보다 문제가 생기는 구조를 없애는 편이 낫습니다. 폼을 페이지로 옮기자 같은 계열의 모바일 문제가 함께 사라졌습니다.
- 배포가 서버에 손으로 남긴 상태에 기대면, 그 상태가 사라지는 날 깨집니다. 서버의 레지스트리 토큰이 지워져 배포가 실패했고, 이후 배포마다 일회용 토큰을 쓰게 바꿨습니다.



