尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證꿈 해몽 로또번호추첨기
翻譯準備中
正在載入完整 README。 以下為已儲存的摘錄。
해몽루는 꿈해몽 로또번호 추첨기입니다. 꿈 내용을 입력하면 꿈해몽을 보여 주고, 꿈에 나온 상징을 바탕으로 로또 번호(1~45 중 6개) 5게임을 추천하는 재미용 웹사이트입니다. 재미로 보는 서비스이며, 추천 번호는 당첨 확률과 무관합니다. • 기술: Next.js 16 (App Router) · TypeScript · Tailwind CSS 4 · Vitest • 1단계(MVP): AI API, 외부 DB, 광고 코드 없이 로컬에서 완전히 동작 Node.js 20 이상이 필요합니다. 개발 모드(npm run dev)에서는 광고가 들어갈 자리가 점선 상자로 보입니다. 빌드 결과에서는 빈 칸입니다.檢視完整 README ↗
해몽루는 꿈해몽 로또번호 추첨기입니다. 꿈 내용을 입력하면 꿈해몽을 보여 주고, 꿈에 나온 상징을 바탕으로 로또 번호(1~45 중 6개) 5게임을 추천하는 재미용 웹사이트입니다.
재미로 보는 서비스이며, 추천 번호는 당첨 확률과 무관합니다.
Node.js 20 이상이 필요합니다.
npm install # 처음 한 번: 필요한 패키지 설치
npm run dev # 개발 서버 실행 → http://localhost:3000
| 명령어 | 하는 일 |
|---|---|
npm run dev | 개발 서버 (코드를 고치면 바로 반영) |
npm test | 테스트 실행 (Vitest) |
npm run build | 배포용 빌드 (사전 상세 131개와 카테고리 페이지 6개를 미리 생성) |
npm start | 빌드한 결과 실행 |
npm run typecheck | 타입 검사만 |
npm run keywords | 꿈 관련 검색 키워드 수집 (네이버 검색광고 API 키 필요, 12장) |
npm run check:launch | 출시 전 점검: 문의 메일·정책 문구·사이트 주소·robots/sitemap·ads.txt·빌드 결과(소유 확인 태그, 광고 스크립트 없음). 문제가 있으면 목록을 보여 주고 실패 (13장) |
npm run test:e2e | 자동 화면 테스트 (휴대폰·PC 크기, Playwright, 13장) |
npm run review:screenshots | 검토용 화면 캡처 → review/screenshots/ (13장) |
| 주소 | 내용 |
|---|---|
| http://localhost:3000/ | 꿈 입력 → 결과 페이지(/result/…)로 이동, 인기 키워드 |
| http://localhost:3000/dream | 카테고리별 꿈해몽 사전 목록 |
| http://localhost:3000/dream/pig | 사전 상세 페이지 예시 (돼지 꿈) + 상황별 풀이 |
| http://localhost:3000/guide | 꿈 가이드 칼럼 5편 |
| http://localhost:3000/about · /privacy · /terms · /contact | 소개·개인정보처리방침·이용약관·문의. 처리방침은 2026-10-01에 실제 동작 기준으로 전면 개정, 최종 개정·시행일 2026-10-04 (날짜와 변경 이력은 app/privacy/page.tsx의 EFFECTIVE_DATE·REVISED_DATE·HISTORY, 운영자·보호책임자·문의 메일은 lib/site.ts). 변경 공지: 중요한 변경은 시행 7일 전부터, 그 밖의 수정은 시행일과 함께 알리고 변경 이력에 남김(15번) |
| http://localhost:3000/sitemap.xml · /robots.txt | 검색엔진용 파일 |
개발 모드(npm run dev)에서는 광고가 들어갈 자리가 점선 상자로 보입니다. 빌드 결과에서는 빈 칸입니다.
app/ 화면(페이지)과 API
page.tsx / 홈
api/interpret/route.ts POST /api/interpret
dream/page.tsx /dream 사전 목록
dream/[slug]/page.tsx /dream/돼지 등 사전 상세 (SSG + 메타 태그)
dream/category/[slug]/ /dream/category/love 등 카테고리별 모음 페이지 6개 (SSG)
result/[payload]/page.tsx 본인 결과 페이지 (해몽하기 뒤에 여는 곳, + 미리보기 이미지)
r/[payload]/page.tsx 공유 링크 결과 페이지 (같은 화면, 버튼만 '나도 해몽 받기') (+ 미리보기 이미지)
guide/, guide/[slug]/ 꿈 가이드 목록과 칼럼 (SSG + 공유 이미지)
about, privacy, terms, contact/
opengraph-image.tsx 기본 공유 미리보기 이미지 (사전 상세는 dream/[slug]/opengraph-image.tsx)
sitemap.ts, robots.ts, icon.svg, not-found.tsx
assets/fonts/ 공유 이미지용 한글 글꼴 (Pretendard 서브셋, SIL OFL 1.1)
components/ 화면 조각 (DreamForm, ResultPage, ResultView, ResultActions, AdSlot, LottoBall, ThemeToggle …)
data/
symbols/*.json 꿈 상징 사전 131개, 상황 풀이 549개 (카테고리별 파일: animal·person·nature·behavior·object·love)
actions.json 꿈속 행동 사전 (들어오다, 쫓기다, 먹다 … 66개, 활용형 포함)
guides.json 꿈 가이드 칼럼 5편
lib/ 핵심 로직 — 화면과 분리되어 있어 테스트하기 쉬움
normalize.ts 전처리 (정규화, 500자 제한, 빈 입력 거부)
matcher.ts 꿈 문장에서 상징 + 행동 찾기 → 상황 풀이 선택
lotto.ts 번호 생성 (순수 함수)
symbolNumbers.ts 상징 → 행운 숫자 후보 규칙
interpret/ 해몽 생성기 (Provider)
service.ts 전체 흐름 조립
symbols.ts, actions.ts 사전 데이터 불러오기 + 유효성 검사
categories.ts 사전 카테고리 6개 (카테고리 페이지 주소, 숫자 후보 구간, 소개 문장)
seo.ts 페이지 메타데이터 + 구조화 데이터(JSON-LD)
og.tsx 공유 미리보기 이미지(1200×630) 공통 틀
share.ts 결과·공유 주소 값 인코딩/디코딩/검사, 다시 뽑기 값
resultMeta.ts 결과 페이지 제목("돼지 꿈 해몽 결과")·noindex 메타데이터
suggest.ts 입력 도우미 (추천 단어)
theme.ts 화면 테마 규칙 (기본 다크, 라이트 선택 기억, 그리기 전 적용 스크립트)
date.ts, site.ts, types.ts
public/fonts/pretendard/ 사이트 글꼴 (Pretendard 한글 2,350자 서브셋, 굵기 400·600·700)
public/ads.txt 애드센스 판매자 한 줄 (사이트 확인용, 광고 코드 아님. /ads.txt 로 열림)
scripts/collect-keywords.ts 네이버 검색광고 키워드 수집 (npm run keywords)
scripts/check-launch.ts 출시 전 점검 (npm run check:launch)
tests/ Vitest 테스트 (로직 단위)
e2e/ Playwright 화면 테스트 + 검토용 화면 캡처
playwright.config.ts 화면 테스트 설정 (휴대폰·PC)
review/ 검토 자료: screenshots/(화면 캡처 24장), lighthouse.md(점수)
POST /api/interpret)요청: { "dream": "꿈 내용", "counter": 0 } (counter는 다시 뽑기 횟수, 생략하면 0)
lib/normalize.ts: 특수문자·이모지 제거, 공백 정리, 영문 소문자화. 빈 입력과 500자 초과는 400 오류로 거부합니다(잘라내지 않음).lib/matcher.ts
물고기로만 잡히고 물로는 잡히지 않습니다. "산불"은 불로 잡힙니다.strict 상징(말·눈·새·별·달·술·쌀·아버지·일진처럼 이름이 다른 뜻으로 흔히 쓰이는 상징)은 더 엄격하게 찾습니다. 표현이 단어 첫머리에서 시작할 때만 인정하고("할아버지" ≠ 아버지, "냄새가" ≠ 새), 한 글자 이름은 "말 꿈", "말꿈"처럼 바로 뒤에 '꿈'이 올 때만 인정합니다. 나머지는 동의어의 구체적인 표현("말을 타", "눈이 내리")으로 찾아서 "말을 했다", "눈을 떴다", "새 옷"은 잡지 않습니다.contextTerms + contextWords): 같은 문장에 문맥 단어가 있을 때만 인정하는 표현입니다. 이별의 "헤어졌·헤어지·갈라섰·차였"은 연인·남자친구·여자친구·남편·아내 같은 말이 같은 문장에 있을 때만 이별로 잡습니다("친구와 놀다 헤어졌어요"는 이별이 아님). '이별'이라는 말은 문맥 없이도 잡습니다.exclude): 단어 첫머리에서 시작하는 이 표현과 겹치는 자리는 그 상징으로 잡지 않습니다. 자녀는 "손녀딸"(의 '딸이'. 손녀딸은 손주로 잡힙니다), 손주는 "손주름·손자국·손자루·손자병법", 달은 "한 달·다음 달·몇 달·지난 달"과 "달이는(약을 달이다)", 금은 "금이 가·금이 갔"(깨짐), 나무는 "나무라다", 번개는 "벼락치기·벼락부자·번개팅", 학교는 "대학교수", 깡패는 "백수건달"을 뺍니다. 일진은 운세 용어("오늘의 일진", "일진이 안 좋·사나·좋", "일진 운세", "일진일퇴" 등)를 뺍니다.data/actions.json의 활용형(예: 들어오·들어와·들어왔)으로 찾습니다. 2글자 활용형("먹는", "타는")은 단어 첫머리에서만 인정해서 "도와주는", "불타는" 같은 오인식을 막습니다.situations에 있는 행동 중 마지막(결말) 것을 고릅니다.
예) "도둑이 들어와서 지갑을 훔쳐 갔다" → 훔쳐 가는 꿈, "물고기를 잡았다가 놓쳤다" → 놓치는 꿈lib/lotto.ts)SHA-256("정규화된 꿈|YYYY-MM-DD")의 앞 4바이트 (날짜는 Asia/Seoul 기준)Math.random은 쓰지 않음, 테스트로 확인)seed + counter)numbers에 weight를 더해 점수표를 만듭니다. 여러 상징이 같은 숫자를 가지면 점수를 합칩니다.돼지 꿈 → 재물처럼 붙습니다.행운 보충입니다.data/symbols/*.json의 numbers)모든 상징의 numbers는 아래 규칙 하나로 계산합니다. 코드는 lib/symbolNumbers.ts에 있습니다.
카테고리마다 숫자 구간이 정해져 있습니다.
| 동물 | 사람 | 자연 | 행동 | 물건 |
|---|---|---|---|---|
| 1–9 | 10–18 | 19–27 | 28–36 | 37–45 |
연애·결혼 카테고리는 구간이 따로 없어 사람 구간(10–18) 을 씁니다. 구간은 9칸씩 5개뿐이고 lib/symbolNumbers.ts는 바꾸지 않기 때문에, 카테고리 → 구간 대응만 lib/categories.ts의 numberCategory()에 둡니다. 그래서 사람에서 연애·결혼으로 옮긴 애인·전 애인의 숫자 후보도 그대로입니다.
대표 숫자 = 구간 시작 + (키워드 글자들의 유니코드 값 합 ÷ 9의 나머지). 띄어쓰기는 빼고 계산합니다.
대표 숫자에서 시작해 17씩 더해 가며 숫자를 만듭니다. 45를 넘으면 45를 빼서 1~45 안으로 돌립니다. 17은 45와 서로소라서 5개까지 절대 겹치지 않습니다.
개수 = weight + 2 (weight 1 → 3개, 2 → 4개, 3 → 5개). 저장할 때는 오름차순으로 정렬합니다.
예) 돼지 (동물, weight 3) '돼'(46076) + '지'(51648) = 97724이고, 97724 ÷ 9의 나머지는 2입니다. 대표 숫자는 1 + 2 = 3이고, 17씩 더하면 3 → 20 → 37 → 54−45 = 9 → 26입니다. 정렬하면 **[3, 9, 20, 26, 37]**입니다.
tests/symbols.test.ts가 모든 상징이 이 규칙을 지키는지 검사합니다.
data/symbols/animal.json 등)에 항목을 추가합니다. numbers는 일단 []로 둡니다.npm test를 실행하면 실패 메시지에 ○○ 의 numbers 는 [..] 이어야 합니다라고 정답 숫자가 나옵니다. 그 숫자를 그대로 복사해 넣으세요.npm test → 통과하면 끝입니다. npm run build 때 사전 페이지가 자동으로 생깁니다.필드 규칙: slug(영문 kebab-case, 중복 불가, 한 번 정하면 바꾸거나 지우지 않음 — 공유 링크가 slug를 씀), keyword, synonyms[], category(동물/사람/자연/행동/물건/연애·결혼, 파일과 일치), meaning(23문장), 5개, 1fortune_type(재물/연애/건강/직장/주의/변화/마음 — 화면 이름은 lib/types.ts의 FORTUNE_LABEL: 재물운·연애운·건강운·직장운·주의운·변화운·마음 풀이. 죽음·장례식은 변화, 돌아가신 가족·병원·귀신·조상·깡패·일진은 마음처럼 민감한 소재에는 건강·재물 유형을 쓰지 않습니다), numbers[](345), 3), weight(1body(300자 이상, 문단은 빈 줄로 구분), situations[](3~5개), strict·contextTerms/contextWords·exclude(선택, 위 매칭 규칙 참고), sensitive(선택. 죽음·돌아가신 가족·장례식·병원·귀신·조상·깡패·일진처럼 민감한 소재는 true로 두어 홈 '인기 꿈 키워드'와 결과 화면의 단어 칩, 홈 예시 꿈에 내보내지 않습니다. 사전 페이지·검색·매칭은 그대로).
풀이 원고는 CLAUDE.md의 사전 콘텐츠 작성 규칙(새로 쓰기, 단정하지 않는 해요체, 당첨·질병·실제 인물 단정 금지 등)을 따릅니다. body에 [태몽 가이드](/guide/taemong)처럼 쓰면 사이트 안 링크가 되고, 없는 페이지로 가는 링크는 tests/categories.test.ts가 잡습니다.
동의어 규칙: 새로 넣는 상징에는 한 글자 동의어를 넣지 않습니다(다른 뜻으로 잘못 잡히기 쉬워서. 기존 30개의 소·용·물 등은 그대로 둡니다). 띄어쓰기 변형("전 애인", "전애인")은 둘 다 넣고, 다른 뜻으로도 쓰이는 말은 구체적인 표현으로 한정합니다. 다른 상징의 표현과 겹치면 tests/dictionary-quality.test.ts가 알려 주니, 어느 쪽으로 잡을지 정해서 그 파일의 목록에 적습니다. 이름이 한 글자인 동음이의어(술, 쌀 등)는 strict로 두고 "술을·술잔·소주"처럼 구체적인 표현으로 찾으며, 오탐 방지 입력을 tests/symbol-inputs.test.ts에 함께 적습니다. 기존 상징의 표현을 새 상징으로 옮길 때는 두 상징의 slug·숫자 후보를 그대로 두고 옮긴 곳을 notes.md에 남깁니다(사전 확장 3차: 조상의 "돌아가신" → 돌아가신 가족, 죽음의 "장례식" → 장례식).
| 파일 | 카테고리 | 상징 | 상황 풀이 |
|---|---|---|---|
animal.json | 동물 | 18 | 82 |
person.json | 사람 (깡패·건달, 일진 포함) | 28 | 122 |
nature.json | 자연 | 18 | 73 |
behavior.json | 행동 | 19 | 76 |
object.json | 물건 (학교·회사·병원 같은 장소와 음식 포함) | 32 | 128 |
love.json | 연애·결혼 | 16 | 68 |
| 합계 | 131 | 549 |
사전 규모는 사전 확장 4차(2026-10-04, 깡패·건달과 일진 추가) 기준입니다. 행동 사전은 66개입니다(4차에서 '괴롭힘을 당하다' 추가).
situations){ "action": "enter", "title": "돼지가 집으로 들어오는 꿈", "meaning": "2~3문장 풀이" }
action은 data/actions.json의 slug여야 합니다. 한 상징 안에서 같은 행동은 한 번만 씁니다.title은 "~꿈"으로 끝나야 합니다. 사전 페이지의 소제목과 결과 화면 라벨로 쓰입니다.#situation-행동slug 주소로 바로 이동할 수 있습니다. 예) /dream/pig#situation-enterdata/actions.json){ "slug": "enter", "verb": "들어오다", "synonyms": ["들어오", "들어와", "들어왔", "들어온"] }
synonyms에는 문장에 실제로 나오는 활용형 앞부분을 넣습니다. 들어오는, 들어오고는 "들어오" 하나로 잡히고, "들어왔다"는 모양이 달라서 "들어왔"을 따로 넣어야 해요.0/500 형식, 450자를 넘으면 경고 색으로 바뀝니다.matchSymbols)lib/suggest.ts)/result/…)로 이동합니다. 두 번 눌러도 요청은 한 번만 갑니다. 실패하면 입력창 아래에 안내를 띄우고 입력 내용은 그대로 둡니다.components/dreamDraft.ts, 개인정보처리방침 7번)/result/[payload], /r/[payload], DB 없음)/result/[payload]**로 이동합니다. 공유받은 사람은 **/r/[payload]**를 엽니다. 두 페이지는 같은 값을 쓰고 같은 화면(components/ResultPage.tsx)을 보여 주며 버튼만 다릅니다.
router.replace 합니다. 방문 기록이 쌓이지 않아 뒤로 가기 한 번이면 입력창으로 돌아가고, 새 번호가 바로 보이도록 번호 제목 위치로 옮깁니다. (lib/share.ts의 nextDrawShare)/r/ 주소를 씁니다.[1, 시드, "2026-10-01", 다시뽑기횟수, ["pig","enter"], ["fire"]] → /r/WzEsMTQx…
SHA-256(정규화된 꿈|날짜)의 앞 4바이트라 원문을 되돌릴 수 없어요. 번호는 lib/lotto.ts의 generateGamesFromSeed()로 같은 규칙대로 다시 계산합니다.noindex, sitemap 제외, robots.txt로는 막지 않음). 제목은 "돼지·불 꿈 해몽 결과 | 해몽루" 형태이고, 대표 주소(canonical)는 /r/ 주소로 맞춥니다.next/og로 요청이 올 때 만듭니다(해몽 요약 + A게임 번호, lib/resultOg.tsx).tests/share.test.ts (인코딩/디코딩, 잘못된 값 21가지, 원문 미포함, 공유 결과 = 원래 결과), tests/result-page.test.ts (같은 값 = 같은 번호, 다시 뽑기 값, noindex·sitemap·robots)/guide)data/guides.json)[돼지 꿈](/dream/pig) 표시는 사이트 안 링크로 바뀝니다(components/RichText.tsx). 사전 상세 페이지에는 그 상징을 다루는 가이드가 "함께 읽으면 좋은 글"로 자동으로 붙어요.tests/guides.test.ts가 본문 1,500자 이상(소제목 제외, 띄어쓰기 포함), 사전 링크 3개 이상, 없는 페이지로 가는 링크 없음을 검사합니다. 새 글을 추가할 때도 같은 기준이 적용돼요.lib/seo.ts의 pageMetadata()로 제목·설명·대표 주소(canonical)·공유 미리보기(OG, 트위터 카드)를 한 번에 채웁니다.next/og로 빌드할 때 PNG를 미리 만듭니다. 사전 상세 131개는 제목·대표 풀이·행운 숫자가 들어간 각자의 이미지를, 나머지 페이지는 사이트 기본 이미지를 씁니다.
assets/fonts의 Pretendard 서브셋(자주 쓰는 한글 2,350자)을 씁니다. 새 글을 추가했을 때 서브셋에 없는 글자가 있으면 tests/seo.test.ts가 알려 줍니다.WebSite, 사전 목록 CollectionPage+ItemList, 사전 상세·가이드 Article+BreadcrumbList. 리치 결과 테스트로 확인할 수 있어요./dream/category/animal·person·nature·behavior·object·love 6개. 사전 목록의 카테고리 제목 옆 "○○ 꿈 모아 보기"와 사전 상세의 이동 경로(꿈해몽 사전 › 카테고리)에서 들어갑니다. 구조화 데이터는 CollectionPage+ItemList입니다.lastmod(= lib/site.ts의 CONTENT_UPDATED_AT)와 함께 알립니다. 사전 내용을 크게 고치면 이 날짜를 바꿔 주세요.app/globals.css의 --page, --surface, --ink 등. Tailwind에서는 bg-surface, text-ink-soft, border-line처럼 씁니다. 색을 바꿀 땐 이 파일만 고치면 돼요.theme), 다시 다크를 고르면 그것도 기억합니다. 저장값이 없거나 알 수 없는 값이면 다크입니다. 규칙은 lib/theme.ts 한 곳에 있습니다.
<html data-theme="dark">, theme-color #0b1026, color-scheme dark(app/layout.tsx의 viewport). 그래서 정적 페이지 캐시도 그대로이고 자바스크립트를 꺼도 다크로 보입니다.<head>의 짧은 스크립트가 화면을 그리기 전에 data-theme="light"로 바꾸고, 라이트용 theme-color(#10173a)·color-scheme(light) 메타를 <head> 맨 앞에 넣습니다(같은 이름이면 앞의 것이 쓰임). 깜빡임이 없고, Next가 만든 다크 메타는 건드리지 않아 하이드레이션 경고도 없습니다.app/globals.css의 기본 :root가 다크, :root[data-theme="light"]가 라이트입니다. 기기 설정(prefers-color-scheme)은 쓰지 않습니다.tests/theme.test.ts가 globals.css의 토큰을 직접 읽어 두 테마 모두 계산합니다. 화면에서 실제로 쓰는 조합(입력창·버튼·고지 상자·광고 자리 표시·운세 태그·밤하늘 띠 포함)은 이전 최솟값 4.94 이상이어야 하고, 지금 가장 낮은 값은 라이트 5.33(흐린 글자 on 페이지 바탕), 다크 6.63(흐린 글자 on 카드), 로또 공 6.03(21~30 빨강)입니다. 앞으로 새로 쓸 수 있는 조합(글자 토큰 × 바탕 토큰 전부)도 4.5 이상인지 함께 확인합니다.public/fonts/pretendard). 제작자가 배포하는 KS X 1001 한글 2,350자 서브셋을 굵기 400·600·700 세 파일(합계 약 790KB)만 씁니다.
app/layout.tsx). 글꼴을 기다리느라 화면이 늦게 뜨지 않아요.review/lighthouse.md.lib/ballColors.ts, 화면과 공유 이미지가 같이 씀). 꿈에서 나온 번호는 달빛색 테두리로 구분합니다..env.example을 복사해 .env.local을 만듭니다. 1단계에서는 아무것도 넣지 않아도 동작합니다.
NEXT_PUBLIC_SITE_URL: 사이트 대표 주소. 비워 두면 https://www.haemongru.com 입니다(lib/site.ts의 DEFAULT_SITE_URL, 끝 슬래시 없이 다듬음). 주소는 이 한 곳에서만 정하고, metadataBase·canonical·공유 미리보기(OG/Twitter)·sitemap·robots·구조화 데이터(url, @id)·공유 링크가 모두 이 값을 씁니다. vercel.app 주소로 열어도 canonical은 이 주소를 가리킵니다. 로컬에서 복사한 공유 링크를 그대로 열어 보려면 .env.local에 NEXT_PUBLIC_SITE_URL=http://localhost:3000을 넣으세요.NAVER_AD_*: 키워드 수집 스크립트용 (아래 12장). 사이트 실행에는 필요 없습니다.LLM_API_KEY, Supabase, AdSense)는 2단계용 자리입니다. 실제 값은 .env.local이나 Vercel 환경변수에만 넣고, git에는 절대 올리지 마세요. (.env.local은 .gitignore에 들어 있어 git에 올라가지 않습니다.)scripts/collect-keywords.ts)네이버 검색광고 API의 키워드 도구로 "꿈" 관련 연관 키워드와 **월간 검색량(PC/모바일)**을 모읍니다. 어떤 꿈을 사전에 먼저 추가할지 고를 때 씁니다.
.env.local에 넣습니다. (없으면 cp .env.example .env.local로 만든 뒤 채우기)
NAVER_AD_API_KEY=발급받은_액세스라이선스
NAVER_AD_SECRET_KEY=발급받은_비밀키
NAVER_AD_CUSTOMER_ID=숫자로된_CUSTOMER_ID
npm run keywords # 기본 씨앗 단어: 꿈해몽·꿈풀이·로또꿈·태몽·길몽 + 사전 상징마다 "○○꿈"
npm run keywords -- 고양이꿈 시험꿈 # 씨앗 단어를 직접 지정
결과는 data/keywords.csv에 저장됩니다.
| 열 | 뜻 |
|---|---|
keyword | 연관 키워드 ("꿈"이 들어간 것만) |
pc, mobile | 월간 검색량. 네이버가 < 10으로 주는 값은 <10으로 적고 합계에는 0으로 셉니다 |
total | pc + mobile (이 값이 큰 순서로 정렬) |
in_dictionary | 이미 사전에 있는 상징이면 Y |
symbols | 해당 상징 slug (예: pig) |
seeds | 이 키워드를 돌려준 씨앗 단어 |
NAVER_AD_INTERVAL_MS로 바꿀 수 있어요.tests/collect-keywords.test.ts에 있습니다.npx playwright install chromium # 처음 한 번: 테스트용 브라우저 설치
npm run test:e2e # 휴대폰(Pixel 7)·PC(1280×800) 크기에서 사용자 흐름 검사
npm run review:screenshots # 검토용 화면 캡처 → review/screenshots/
e2e/flow.spec.ts: 입력 → 결과 페이지로 이동 → 새로고침해도 같은 번호 → 뒤로 가기(입력 복원) → 다시 뽑기(기록 안 쌓임) → 링크 복사 → 공유 페이지에서 같은 결과 → "나도 해몽 받기", 로딩·이중 제출 방지, 실패 안내, 잘못된 결과·공유 주소, 빈 입력, 입력 도우미, 움직임 줄이기, 사전·가이드·404, 사전 목록·카테고리 페이지·sitemap에 모든 상징이 나오는지를 검사합니다.e2e/privacy.spec.ts: 개인정보처리방침에 적은 동작을 확인합니다. 해몽 흐름 내내 사이트 밖으로 나가는 요청이 없고(분석 도구·외부 글꼴·광고 스크립트 없음), 쿠키가 없고, 브라우저 저장소는 theme·dream-draft뿐이며, 꿈 원문은 /api/interpret 요청에만 실리고 결과 주소에는 없는지, 모든 종류의 페이지 푸터에 처리방침 링크가 있는지, 처리방침의 시행일·목차·보호책임자를 검사합니다. 분석 도구나 광고 코드를 넣으면 실패하니, 그때는 처리방침을 먼저 고치고 이 테스트를 손봅니다.e2e/theme.spec.ts: 기기를 라이트로 둔 브라우저에서 처음 방문하면 다크, 라이트를 고르면 새로고침·다른 페이지에서도 라이트, 다시 다크도 유지, 잘못된 저장값은 다크, 저장된 라이트는 첫 화면을 그리기 전에 적용(깜빡임 없음, 콘솔 오류 없음), 서버 HTML과 자바스크립트를 끈 화면도 다크, theme-color·color-scheme 메타가 테마와 맞는지 검사합니다.npm run build 후 3100번 포트로 서버를 켭니다. 이미 켜 둔 서버가 있으면 그대로 씁니다. 포트를 바꾸려면 E2E_PORT=3200 npm run test:e2e.theme)으로 정해서 찍습니다.test-results/에 기록이 남습니다(git에는 올라가지 않음).npm run check:launch)배포 직전에 실행하세요. 빌드 없이 몇 초면 끝나고, 하나라도 걸리면 고칠 목록을 보여 주며 실패합니다.
lib/site.ts의 CONTACT_EMAIL이 [email protected] 같은 자리표시자인지OPERATOR_NAME)·보호책임자(PRIVACY_OFFICER_NAME)·문의 메일(CONTACT_EMAIL)이 lib/site.ts 값과 같은지, 다른 메일 주소나 전화번호(0으로 시작하는 번호, +82)가 들어가 있지 않은지. 연락처는 메일만 공개합니다.SITE_URL이 대표 도메인(https://www.haemongru.com)과 같은지, vercel.app·localhost·http 주소가 아닌지public/ads.txt가 애드센스 한 줄(lib/site.ts의 ADS_TXT_LINE)뿐인지.next가 있으면(npm run build 뒤)
<head>에 네이버 소유 확인 태그와 애드센스 사이트 확인 태그(google-adsense-account)가 정확히 한 번씩 있는지문의 메일(
[email protected])을 넣어 지금은 통과합니다. 배포 전에 한 번씩 돌려 보세요.
점수와 개선 내역은 review/lighthouse.md에 있습니다. 직접 재 보려면 크롬이 설치된 컴퓨터에서:
npm run build && npm start # 터미널 1
npx lighthouse http://localhost:3000/dream/pig --view # 터미널 2: 휴대폰 기준
npx lighthouse http://localhost:3000/dream/pig --preset=desktop --view # PC 기준
점수는 잴 때마다 조금씩 달라지니 3번쯤 재서 가운데 값을 보세요.
https://www.haemongru.com입니다(lib/site.ts). 다른 주소로 띄울 때만 NEXT_PUBLIC_SITE_URL을 넣으세요. (바꾸면 다시 배포해야 반영돼요)vercel.json)은 필요 없습니다.www.haemongru.com입니다. 루트 도메인 → www 이동(308)은 Vercel 대시보드에서만 설정합니다.
next.config.ts의 redirects, middleware/proxy)에는 리디렉션을 넣지 않습니다. 양쪽에서 걸면 서로 보내는 무한 이동(루프)이 생길 수 있어요. tests/domain.test.ts가 코드에 리디렉션이 없는지 확인합니다.https://www.haemongru.com으로 바뀌는지, vercel.app 주소로 열어도 페이지 소스의 canonical이 https://www.haemongru.com/...인지, /sitemap.xml과 /robots.txt의 주소가 https://www.haemongru.com인지 봅니다.| 기능 | 손댈 파일 |
|---|---|
| AI 해몽 | lib/interpret/llm.ts 새로 만들기 (InterpretationProvider 구현), lib/interpret/index.ts에서 LLM_API_KEY가 있으면 LlmProvider를 돌려주도록 변경. 실패하면 RuleBasedProvider로 대체. 번호는 계속 lib/lotto.ts가 생성. 꿈 원문이 외부 AI 업체로 전송되므로 켜기 전에 개인정보처리방침에 처리 위탁·국외 이전 고지를 추가하고 입력창 근처에 안내를 넣어야 합니다(app/privacy/page.tsx 위쪽 주석) |
| Supabase | lib/symbols.ts의 함수 내용만 DB 조회로 교체 (함수 이름 유지). 저장이 생기면 app/privacy/page.tsx 수정 |
| 공유 링크 | 1단계에서 DB 없이 구현 완료(/r/[payload]). AI 해몽 문장까지 그대로 공유하려면 Supabase에 결과를 저장하고 app/r/[payload]/page.tsx가 저장된 결과를 읽도록 바꾸기 |
| 애드센스 | 사이트 확인 준비 완료(2026-10-04): 모든 페이지 <head>에 google-adsense-account 메타 태그(app/layout.tsx의 metadata.other), public/ads.txt 한 줄. 게시자 ID는 lib/site.ts의 ADSENSE_PUBLISHER_ID 한 곳에 있습니다. 둘은 광고 코드가 아니라서 개인정보처리방침 8번 "광고 코드도 넣지 않았습니다"는 그대로 사실입니다. 광고 스크립트는 아직 넣지 않았습니다. 승인 뒤에는 notes.md의 "승인 후 해야 할 일" 순서를 따릅니다: ① 처리방침 8번을 실제 게재 기준으로 고치고 시작일을 알린 뒤 배포 ② 광고 스크립트(app/layout.tsx)와 광고 단위(components/AdSlot.tsx) 넣기 ③ e2e/privacy.spec.ts·scripts/check-launch.ts·tests/privacy.test.ts(tests/adsense.test.ts 포함)의 광고 코드 검사 고치기 ④ 애드센스 관리 화면에서 EEA·영국·스위스 동의 메시지 설정. 호스팅은 Vercel Pro라 광고 게재(상업적 이용)를 할 수 있습니다. 요금제를 바꾸거나 로그 보관을 늘리면 개인정보처리방침 1·5번의 접속 기록 보관 기간을 먼저 고칩니다(근거와 확인 상태는 notes.md) |
lib/interpret/InterpretationProviderRuleBasedProviderlib/lotto.ts: 아래 규칙대로 만듭니다. Provider(AI)는 번호에 관여하지 않습니다.{
"summary": "…",
"symbols": [{ "slug": "pig", "keyword": "돼지", "meaning": "…", "fortune_type": "재물" }],
"situations": [{ "slug": "pig", "action": "enter", "title": "돼지가 집으로 들어오는 꿈" }],
"games": [{ "numbers": [3, 9, 17, 20, 33, 41], "reasons": ["행운 보충", "돼지 꿈 → 재물", "…"] }],
"date": "2026-09-30",
"counter": 0
}
reasons[i]는 numbers[i]를 뽑은 이유입니다. slug(사전 링크용)와 date, counter는 화면 표시용으로 추가한 필드입니다.
situations에는 상황 풀이가 적용된 상징만 들어 있고, 그 상징의 symbols[].meaning은 상황 풀이 문장입니다.prefers-reduced-motion)를 켜면 애니메이션 없이 바로 보여 줍니다.돼지 → 재물, 죽는 꿈 → 변화처럼 운세 색 태그가 붙습니다. (lib/reasonTag.ts가 lotto.ts의 이유 문장을 화면용으로만 다듬고, 번호 규칙은 건드리지 않아요)