토스 미니앱으로 만든 오늘꿈해몽 의 데이터를 그대로 써서, 검색용 정적 페이지 2,400장을 스크립트 하나로 만들고 GitHub Pages 하위 경로에 올린 과정을 정리합니다.
왜 정적 페이지를 따로 만들었나
- 앱에는 꿈 2,001개, 상황별 풀이 9,327개가 JSON 으로 들어있음
- 앱은 이걸 JS 로 그려서 보여주는데, 크롤러 대부분은 JS 를 안 돌림
- “뱀 꿈” 을 검색한 사람이 들어올 문이 없는 상태
- 그래서 같은 JSON 을 읽어서 꿈 하나당 HTML 한 장을 뽑는
build-web.mjs를 만듦- 프레임워크 없이 Node 스크립트 하나, 템플릿 문자열로 HTML 을 찍어냄
- 결과물
web-dist/를 공개 레포에 그대로 올리면 GitHub Pages 가 서빙
데이터 없이 규칙으로 만드는 페이지
- 꿈 데이터만으로는 검색어 종류가 한정적이라, 사람들이 매년 찾는 걸 계산으로 붙임
- 나이·띠 — 1940년생부터 올해생까지 년생마다 한 장
- 띠는
((year - 4) % 12 + 12) % 12, 간지는 천간 10개를 같은 방식으로 돌림 - 올해 연도를 빌드 시점에서 읽으니, 해가 바뀌고 다시 빌드하면 나이가 다 같이 올라감
- 띠는
- 삼재 — 띠를 네 묶음으로 나누면 묶음마다 들어오는 해가 정해져 있음
- 쥐·용·원숭이는 호랑이해부터, 소·뱀·닭은 돼지해부터 … 이런 식이라 시작 지점이
(2 - 3g) mod 12로 떨어짐 -
들삼재·눌삼재·날삼재는 시작 지점에서 0·1·2년 차이
1
2
3const sjStart = (g) => (((2 - 3 * g) % 12) + 12) % 12; // g = 띠 index % 4 // 출력 결과: g=0 → 2(호랑이해), g=1 → 11(돼지해), g=2 → 8(원숭이해), g=3 → 5(뱀해)
- 쥐·용·원숭이는 호랑이해부터, 소·뱀·닭은 돼지해부터 … 이런 식이라 시작 지점이
- 손없는날 — 음력 날짜 끝자리가 9·0 인 날
- 음력 변환은 직접 짜지 않고
korean-lunar-calendar(MIT) 를 씀 - 2027년 2월 6일이 음력 12월 30일(설 전날)로 나오는 걸 보고 맞는지 확인함
- 음력 변환은 직접 짜지 않고
- 공휴일 — 대체공휴일 규칙이 생각보다 갈래가 많음
- 삼일절·광복절 같은 국경일은 토·일과 겹치면 다음 평일
- 설·추석 연휴는 토요일은 안 치고 일요일이나 다른 공휴일과 겹칠 때만
- 어린이날은 토·일·다른 공휴일 모두
- 2026년부터 노동절·제헌절이 공휴일이 돼서 규칙에 추가함
- 규칙으로 뽑은 2027년 대체공휴일 7일이 이미 공개된 2027년 달력과 날짜가 전부 같았음
‘꿈 꿈’ 버그
- 데이터에 “돼지꿈” 처럼 이름 자체가 “꿈” 으로 끝나는 항목이 있었음
- 템플릿이
${name} 꿈 해몽이라서 “돼지꿈 꿈 해몽” 이 631장에 찍혀 있었음 - 제목에서 이게 반복되니 검색 결과에서 그대로 노출될 뻔 했음
- 템플릿이
-
이름 끝을 보고 붙일지 말지 정하는 헬퍼 하나로 해결
1
2
3const kkum = (name) => (name.endsWith('꿈') ? name : `${name} 꿈`); console.log(kkum('돼지꿈'), kkum('뱀')); // 출력 결과: 돼지꿈 뱀 꿈- 홈의 검색 JS 에도 같은 조합 로직이 있어서 거기도 같이 고침
sitemap lastmod 를 빌드 날짜로 찍으면 안 됨
- 처음엔 빌드할 때마다 전 페이지 lastmod 를 오늘로 넣고 있었음
-
지금은 페이지가 기대는 데이터 파일의 마지막 git 커밋 날짜를 씀
1
2const gitDate = (f) => execSync(`git log -1 --format=%cs -- ${f}`).toString().trim(); // 꿈 페이지 → data/dreams, 연도 운세 → data/fortune/year-2027.json ...- 날짜가 박힌 페이지(그날의 띠별 운세 등)는 그 날짜를 그대로 씀
- IndexNow 도 같은 기준으로 바꿈
- 매일 배포하면서 2,400개를 매번 보내면 스팸에 가까움
- sitemap 에서 lastmod 가 오늘인 주소만 골라서 보내고, 전부 보내고 싶을 땐
INDEXNOW_ALL=1
구조화 데이터와 llms.txt
- 목록 페이지는
CollectionPage + ItemList, 상세는WebPage, 전부BreadcrumbList - 질문형 소제목이 있는 페이지는
FAQPage를 같이 넣음- “1990년생은 2026년에 몇 살인가요?” 같은 질문 바로 아래 첫 문장에 답을 둠
- 구글 FAQ 리치 결과는 지금 정부·의료 사이트에만 나오지만, AI 검색은 이 구조를 그대로 읽음
llms.txt에 사이트 한 줄 소개와 주요 목록 링크를 둠- og:image(1200×630), favicon.ico, RSS 2.0
feed.xml도 같이 생성
GitHub Pages 하위 경로의 함정
- 사이트가
kangraemin.github.io/oneul-kkum/처럼 하위 경로에 있음- robots.txt 는 호스트 루트(
kangraemin.github.io/robots.txt)만 읽힘 - 하위 경로에 robots.txt 를 둬도 소용 없어서, 블로그 레포 루트 robots.txt 에
Sitemap:줄을 추가함
- robots.txt 는 호스트 루트(
- 네이버 서치어드바이저는 사이트 단위(호스트)로만 등록돼서, 블로그 등록에 RSS 주소만 하위 경로로 따로 넣어야 함
- 서치콘솔은 URL-prefix 속성이라 하위 경로 그대로 등록 가능
- 배포 직후 URL 검사 API 로 보면 전부 “URL is unknown to Google” — 새 경로는 링크로 발견되기 전까지 이 상태가 한동안 감
"Project" 카테고리의 최근 포스팅
카테고리 모든 글 보기| [Project] - 데이터 파일 몇 개로 정적 페이지 2,400장 만들기 (오늘꿈해몽) | 2026. 10. 22 |
|---|---|
| [Project] - 글쓰기 공모전 마감 모음 사이트를 맥 한 대로 만들고 운영하기 | 2026. 10. 19 |