Still Coding은 제가 만든 웹 앱을 한곳에 모아 소개하는 포털입니다. 게임 모음 Direct Play, 퍼즐 Pinhole Lab, 일본어 문자 학습 가나 공방, 협업 도구 CollaBoard, 버스 노선 탐색 Bus Explorer, 악보 스튜디오 Songnote, 음정 확인 도구 Vocal Check가 각자 하위 도메인에서 따로 운영됩니다. 이 글은 포털 자체를 어떤 구조로 만들고 운영하는지 정리한 기록입니다.
1. 앱은 따로, 포털은 가볍게
앱마다 기술 스택이 다릅니다. Songnote는 React와 Vite, Vocal Check는 프레임워크 없는 순수 자바스크립트, Bus Explorer는 Python FastAPI 서버, CollaBoard와 Direct Play는 Cloudflare Worker와 Durable Objects를 씁니다. 이것들을 하나의 저장소나 하나의 배포로 묶으면 한 앱의 변경이 다른 앱을 멈추게 할 수 있습니다.
그래서 원칙을 정했습니다.
- 앱은 각자의 하위 도메인과 저장소, 배포 파이프라인을 가진다.
dp.still-coding.cc,vocal-check.still-coding.cc처럼 나눕니다. - 포털은 앱을 호출하지 않는다. 포털은 앱의 소개, 사용 방법, 데이터 처리 방식을 설명하고 링크만 겁니다. 앱이 잠시 멈춰도 포털은 영향을 받지 않습니다.
- 포털은 서버 코드 없이 정적 파일만 배포한다. 공격 표면과 운영 부담을 최소로 줄이기 위해서입니다.
2. 왜 Astro인가
포털에 필요한 것은 거의 전부 “빌드할 때 정해지는 HTML”입니다. 앱 목록, 앱 상세, 개인정보처리방침, 개발 노트 모두 방문자마다 달라질 이유가 없습니다. Astro는 기본적으로 자바스크립트를 보내지 않고 HTML과 CSS만 만들어 내며, 필요한 곳(공유 버튼, QR 코드 대화상자, 언어 선택 메뉴)에만 작은 스크립트를 붙일 수 있습니다.
- 검색 엔진과 광고 심사 크롤러가 자바스크립트를 실행하지 않고도 모든 내용을 읽을 수 있습니다.
- 앱 데이터는
src/data/apps.ts한 파일에 모았습니다. 제목, 요약, 대상 사용자, 시작 3단계, 데이터 정책, FAQ를 여기서 관리하고, 홈 카드와 앱 상세 페이지가 같은 데이터를 씁니다. 설명이 두 군데에서 어긋나는 일을 막기 위해서입니다. - 개발 노트는 Astro 콘텐츠 컬렉션으로 Markdown 파일을 읽어, 제목·설명·날짜·관련 앱을 스키마로 검증합니다. 날짜 형식이 틀리거나 필수 항목이 빠지면 빌드가 실패합니다.
3. Cloudflare Workers 정적 에셋으로 배포
빌드 결과(dist/)는 Cloudflare Workers의 정적 에셋 기능으로 배포합니다. Worker 스크립트 없이 wrangler.jsonc에 에셋 디렉터리와 커스텀 도메인만 적으면 됩니다.
{
"name": "still-coding-portfolio",
"assets": { "directory": "./dist", "not_found_handling": "404-page" },
"routes": [{ "pattern": "still-coding.cc", "custom_domain": true }]
}
여기서 신경 쓴 설정이 not_found_handling입니다. 단일 페이지 앱(SPA)처럼 모든 경로에 index.html을 돌려주면, 없는 주소도 정상(200)으로 응답하는 soft 404가 됩니다. 크롤러는 이런 사이트에서 어떤 페이지가 진짜인지 판단하기 어렵고, /robots.txt나 /ads.txt를 요청했는데 HTML이 오는 문제도 생깁니다. 포털은 404-page로 두어, 없는 주소에는 404 상태 코드와 함께 안내 페이지를 보여 줍니다.
4. 한국어와 영어, 두 언어 운영
방문자 대부분은 한국어를 쓰지만, 앱 중 일부는 해외 사용자도 씁니다. 그래서 한국어를 기본(/)으로, 영어를 /en/ 아래에 두었습니다.
- 모든 페이지의
<head>에hreflang="ko",hreflang="en",x-default대체 링크를 넣어 검색 엔진이 언어별 페이지를 짝지을 수 있게 했습니다. - 한국어로만 쓴 페이지(이 개발 노트처럼)에는 대체 링크를 넣지 않습니다. 없는 영어 페이지를 가리키면 오히려 신호가 꼬이기 때문입니다.
- 앱마다 영어 지원 수준이 다릅니다. 영어 화면이 준비된 앱은 영어 포털에서 앱의
/en/주소로, 준비되지 않은 앱은 기본 주소로 연결합니다.englishReady플래그 하나로 이 분기를 관리합니다. - 번역은 기계적으로 옮기지 않고, 영어 문장을 따로 다듬었습니다. 같은 내용을 두 언어로 복제해 분량만 늘리는 것은 방문자에게도 검색 엔진에게도 도움이 되지 않습니다.
5. 신뢰를 위한 페이지들
개인이 운영하는 사이트일수록 “누가, 어떻게 운영하는가”를 분명히 보여 주는 것이 중요합니다. 포털에는 다음 페이지를 두었습니다.
- 소개: 운영자와 사이트의 목적
- 개인정보처리방침: 포털이 처리하는 정보, 앱별 저장 방식, 광고 쿠키와 해제 방법
- 이용약관: 무료 서비스의 조건과 한계
- 문의: 운영자에게 직접 닿는 이메일 주소
특히 개인정보처리방침은 앱별로 실제 코드와 대조해 적었습니다. 예를 들어 Vocal Check는 녹음 파일을 만들지 않고, Songnote는 악보를 브라우저에만 저장하며, CollaBoard는 팀 자료를 서버에 두지 않습니다. 이런 차이를 뭉뚱그리지 않고 앱마다 밝히는 편이 사용자에게 정직합니다.
6. 운영하면서 챙기는 체크리스트
robots.txt와sitemap.xml이 HTML이 아니라 정상적인 텍스트와 XML로 응답하는가- 사이트맵에 실제로 공개한 페이지만 들어 있는가(빌드 시 자동 생성)
- 대표 주소가 하나인가(
www와workers.dev같은 보조 주소는 리다이렉트하거나 막기) - 홈에서 모든 정책 페이지까지 두 번 이내의 클릭으로 갈 수 있는가
- 앱 설명의 숫자(게임 수, 도구 수)가 실제 앱과 일치하는가
정리
여러 앱을 운영하는 개인 개발자에게 포털은 “모든 것을 담는 플랫폼”보다 가볍고 정직한 안내판이 더 잘 맞았습니다. 앱은 각자 독립적으로 배포하고, 포털은 정적 HTML로 설명과 연결만 맡습니다. 덕분에 새 앱을 추가하는 일은 apps.ts에 항목 하나를 더하고 상세 설명을 쓰는 것으로 끝납니다.