Mobile Wedding Invitation
Our wedding invitation site, editable from a phone without touching code
- Released
- 2026.08
- Role
- Planning · Development
- Tech
- 7
Preview
About this app
The write-up below is in Korean. For an English overview of every project, see the CV.
왜 만들었나
작업 첫날 오후에 예식장이 정해졌고, 다음 날 아침에 예식 시간이 바뀌었다. 신랑 계좌는 일주일 뒤에야 들어왔고, 신부 계좌와 사진은 지금도 없다. 내용을 YAML 파일로 옮긴 뒤 그 파일을 고친 커밋만 13개다. 청첩장은 보내기 전까지 계속 바뀐다.
바뀔 때마다 PC 앞에 앉아 코드를 열 수는 없다. 그래서 내용을 코드 밖 파일 하나로 빼고, 폰에서 GitHub 로그인만으로 그 파일을 고치면 1~2분 뒤 청첩장이 바뀌게 만드는 것을 목표로 잡았다.
만들면서 기준이 하나 더 생겼다. 청첩장 한 장에는 양가 부모님 성함, 두 사람의 전화번호, 계좌번호가 한꺼번에 실리고, 링크를 받은 사람은 누구나 그걸 본다. 꼭 필요한 것만 화면에 내보낸다.
무엇을 만들었나
우리 결혼식(2027년 3월 13일)에 쓸 한 페이지짜리 모바일 청첩장이다. 두 사람 이름과 일시로 시작해 인사말, 예식 안내와 달력, 갤러리, 오시는 길, 연락처, 마음 전하실 곳이 이어지고, 맨 끝에 축하 메시지 방명록 자리가 있다. Astro로 정적 빌드해 Cloudflare Pages에 올렸고, 검색 엔진에는 걸리지 않게 막았다.

첫 화면. 이름과 일시 아래로 인사말과 양가 줄이 이어진다.
달력은 빌드할 때 예식 날짜로 그리고, 그 아래 D-day는 보는 사람의 브라우저에서 한국 시간 자정 기준으로 센다. 날짜를 바꾸면 둘 다 따라간다.

예식 안내의 달력. 연월과 표는 빌드 때 그리고, 아래 D-day는 보는 날 브라우저에서 센다.
갤러리는 옆으로 넘기는 전폭 사진이다. 사진을 3:4로 잘라 넘길 때 높이가 널뛰지 않게 하고, 화면을 꽉 채운 사진 옆에 더 있다는 단서로 점을 사진 안쪽 아래에 얹었다.

갤러리. 사진 한 장이 폭을 꽉 채우고 옆으로 넘긴다. 지금 든 6장은 레이아웃을 보려고 만든 가짜 사진이다.
디자인은 따로 고르는 중이다. 운영과 다른 브랜치에 같은 실제 정보로 그린 시안을 첫날 13종 만들었다. 정통 스타일 4종, 형식을 바꾼 4종(보딩패스 · 신문 1면 · 스토리 카드 · 폴라로이드), 움직임이 있는 5종(봉투 열기 · 패럴랙스 등)이다. 그날 밤 5종으로 줄였고, 세 묶음에서 고루 남았다. 이어서 전체화면 갤러리 뷰어, 접히는 계좌, 공유 버튼을 공용 부품으로 만들어 스토리 카드를 뺀 네 시안에 각자의 색으로 붙였다.

남은 시안 5종의 첫 화면. 왼쪽부터 클래식 에디토리얼 · 내추럴 웜 · 스토리 카드 · 봉투 열기 · 패럴랙스 & 켄번즈. 사진 자리는 아직 회색 상자다.
화면의 양가 부모님 성함과 예식장 이름·주소는 모자이크했다. 연락처와 계좌가 있는 섹션은 싣지 않았다.
어떻게 만들었나
내용은 YAML, 검사는 Zod. 처음엔 내용이 TypeScript 파일에 있었는데, 관리자 도구로 고른 Sveltia CMS는 TS를 읽지 못하고 YAML은 읽고 쓴다.
그래서 첫날 내용을 wedding.yaml로 옮기고, wedding.ts는 그 YAML을 읽어 Zod로 검사하고 타입만 붙이는 67줄짜리 로더로 줄였다.
관리자는 12줄짜리 HTML 한 장과 설정 파일 하나다. GitHub로 로그인해 폼을 저장하면 YAML이 커밋되고, Pages 자동 배포가 사이트를 바꾼다.
관리자를 위해 서버를 두지는 않았다. GitHub 로그인을 중계하는 작은 Worker 하나를 메인 사이트와 같이 쓸 뿐이다.
빈칸은 숨긴다. 값이 없는 항목은 화면에서 사라진다. 갤러리가 비면 갤러리 섹션이, 계좌가 하나도 없으면 계좌 섹션이 통째로 빠지고, 지도 링크를 비우면 그 버튼만 숨는다. 정해지지 않은 게 많은 청첩장이라 미완성인 채로도 깨진 화면은 나가지 않아야 했다.
검사는 빌드 안에 있다. 관리자가 스키마와 다른 모양으로 저장하면 커밋은 되지만 빌드에서 멈추고, 하객 화면은 직전 배포에 머문다.
잘못된 값이 하객에게 닿지 않는 대신, 관리자 필드(config.yml)와 Zod 스키마는 늘 짝을 맞춰야 한다.
카카오톡 링크 칸을 더할 때도 두 파일과 YAML을 한 커밋에서 같이 고쳤다. 날짜는 따옴표가 없으면 YAML 파서가 Date로 바꿔 버려서,
스키마가 문자열과 Date를 둘 다 받아 문자열로 통일한다.
전화번호 대신 카카오톡 1:1 오픈채팅. 카카오톡은 개인 채팅방을 바로 여는 링크를 주지 않는다. 1:1 오픈채팅은 링크만으로 대화가 열리고 전화번호도 실명도 드러나지 않는다. 링크를 넣은 사람은 연락처 줄이 카카오톡 버튼 하나로 바뀌고 전화번호는 HTML에도 나가지 않는다. 링크가 없는 사람만 전화·문자 버튼이 남으니, 한 명씩 바꿔도 연락 수단이 비는 구간은 없다.
방명록은 이 청첩장의 첫 서버다. 하객이 남기는 글은 git에 커밋할 수 없어서 방명록만 D1에 두고 Pages Functions로 읽고 쓴다.
Astro는 정적 빌드 그대로고 functions/ 폴더만 더했다. 하객은 이름과 메시지, 지울 때 쓸 4자리 비밀번호를 남긴다.
비밀번호는 PBKDF2 같은 반복 해시 대신 환경변수에 둔 열쇠를 섞는 HMAC으로 저장한다. Workers 무료 플랜은 요청당 CPU가 10ms라서다.
4자리는 1만 번이면 다 넣어 볼 수 있으니 삭제 시도도 10분에 10회로 묶어, 전수 대입에 일주일쯤 걸리게 했다.
도배 횟수는 글 목록이 아니라 지워지지 않는 별도 기록에서 센다. 글 수로 세면 쓰고 지우기를 반복해 제한을 피할 수 있다.
백엔드에 닿지 못하면 섹션째 숨는다.
걸렸던 것
파일 하나에 운영과 미리보기 배포가 같이 멈췄다. 방명록을 올리며 로컬 개발용 wrangler.toml을 같이 커밋했다.
Cloudflare Pages는 저장소에 이 파일이 있으면 대시보드 설정 대신 파일을 기준으로 삼는다. 직후 두 브랜치의 배포가 모두 멈췄다.
파일에는 아직 만들지 않은 D1 자리에 예약값을 넣어 둔 상태였고, 그게 원인으로 보였다.
증상은 ’push는 됐는데 몇 분이 지나도 사이트가 안 바뀐다’뿐이라 짚기 어려웠다.
10분 뒤 이 파일을 wrangler.example.toml로 바꿔 저장소에서 빼고 .gitignore에 넣어 복구했다.
’오후 12시’가 ’PM 12:00’으로 나온다. Intl의 한국어 시각 포맷은 ICU 버전에 따라 결과가 다르다.
이 PC에서 같은 시각을 넣어 보면 크롬은 ‘오후 12:00’, Node 24는 ’PM 12:00’을 낸다. 사이트는 Node 24로 빌드되고 날짜는 빌드 때 찍히므로,
첫 커밋부터 숫자만 받아 ’○년 ○월 ○일 ○요일 오후 ○시’를 직접 조립했다.
사진이 없으면 갤러리를 고를 수 없다. 3열 썸네일, 세로로 한 장씩, 가로 스와이프 중 무엇이 나은지는 사진이 들어가야 보인다. 회색 상자나 평면 그라디언트로는 사진에 기대는 배치가 부당하게 불리해서, 사진처럼 읽히는 요소만 SVG로 흉내 낸 생성기를 만들었다. 원경·중경·전경의 흐림 차이, 톤 폭, 지평선, 비네팅, 그레인까지다. 사람은 그리지 않았다. 흐리면 얼룩, 선명하면 일러스트로 읽혀서다. 이 사진 6장을 넣고 375px 폭에서 재 보니, 3열 썸네일일 때 2,949px이던 페이지가 세로로 쌓으면 4,958px로 68% 길어졌다. 가로 스와이프는 3,165px로 7%만 늘면서 사진 크기는 같았다.
200은 ’남아 있다’는 뜻이 아니었다. 시안 8종을 지운 뒤에도 그 주소가 200으로 열려서, 기록에는 ’Pages가 이전 배포의 파일을 지우지 않는다’고 적어 두었다. 이 글을 쓰며 다시 열어 보니 열리는 건 지운 시안이 아니라 청첩장 본문이었다. 아예 없는 주소를 넣어도 똑같다. 이 프로젝트에는 404 페이지가 없고, Pages는 404 페이지가 없는 사이트를 단일 페이지 앱으로 보고 모든 주소에 첫 페이지를 내준다. 상태 코드만 보고 원인을 짐작했던 것이다.
어디까지 왔나
8월 9일 방명록을 올린 밤 이후로 멈춰 있다. 청첩장은 운영 주소에 올라가 있지만 하객에게 보낼 상태는 아니다. 갤러리에는 위의 가짜 사진 6장이 그대로 있다. 생성기는 가짜 사진이 운영에 새지 않게 출력 폴더까지 나눠 두었는데, 레이아웃을 실제 화면에서 보려고 결국 일부러 운영에 올렸다. 신부측 계좌 칸에는 신랑 계좌가 임시로 복사돼 있어, 이대로 보내면 신부측으로 보낸 송금이 신랑 계좌로 간다. 디자인은 5종에서 멈춰, 하객이 보는 화면은 아직 시안이 아닌 기본안이다. 방명록은 코드까지 끝났지만 D1과 시크릿을 넣지 않아 운영 API가 오류를 낸다. 섹션째 숨게 해 둔 덕에 화면에는 드러나지 않는다.
처음 세운 두 기준은 둘 다 절반이다. 폰에서 고친다는 쪽은 관리자를 만들고 로그인이 GitHub로 이어지는 데까지 확인했지만,
wedding.yaml을 고친 커밋 13개는 전부 코드 작업 중에 만든 것이고 관리자에서 저장한 커밋은 아직 하나도 없다.
전화번호를 내보내지 않는다는 쪽은 구조는 끝났는데, 오픈채팅 링크를 아직 만들지 않아 지금 운영 화면에는 전화·문자 버튼이 그대로 있다.
남은 것
남은 일 대부분은 코드가 아니라 폰에서 할 일이다. 두 사람의 1:1 오픈채팅 링크 만들기, 신부 계좌 넣기, 실제 사진 올리기. 카카오페이 버튼은 폰으로 직접 눌러 받는 사람 이름을 확인해야 한다. 송금 링크는 데스크톱에서 404를 내고 받는 사람도 앱 안에서 정해져서, 코드로는 어느 링크가 누구 것인지 알 수 없다. 이 일들이 관리자의 첫 실전이 된다.
그다음은 시안을 2종, 다시 1종으로 줄이는 일이다. 스토리 카드는 전체화면 카드라는 형식이 달라 공용 부품을 아직 붙이지 않았다. 방명록 D1과 시크릿 설정, 가짜 사진과 생성기 정리, 그리고 없는 주소에 청첩장 본문이 열리지 않게 404 페이지를 두는 일이 남았다.
Information
- Made by
- Kipyo Bae (kipioneer)
- Version
- 2026.08
- Role
- Planning · Development
- Tech
- Astro · TypeScript · Zod · Sveltia CMS · Cloudflare Pages · Pages Functions · Cloudflare D1
- Released



