실측(2026-09-07): `curl /` 가 3,021바이트에 본문 0자·`<a>` 0개였다. 같은 호스트의
발행본은 48,072바이트다. 구글은 JS 를 실행하지만 **렌더링 큐가 따로** 돌고 신규
도메인은 뒤로 밀린다 — 그동안 색인에는 "제목만 있고 내용 없는 페이지"로 들어가 있다.
서치콘솔이 "URL이 Google에 등록되어 있음"이라고 답하면서도 브랜드명 검색에조차 안
걸리던 이유다.
스크립트를 새로 짜지 않았다. react-router 7.17 에 프리렌더가 내장돼 있고
`ssr: false` 와 함께 쓰면 런타임 Node 서버 없이 지정한 경로만 HTML 로 굽는다 —
나머지는 지금까지처럼 SPA 폴백이다. 배포 구조가 그대로다.
- react-router.config.ts: `ssr:false` + `prerender: ['/', '/pricing', '/showcase']`.
로그인 뒤에만 의미가 있는 화면은 굽지 않는다(구울 내용이 사용자별이다)
- src/root.tsx · src/routes.ts: 예전 index.html + app/router.tsx 가 하던 일.
가드는 페이지마다 감싸지 않고 RequireAuthLayout 레이아웃 라우트 하나로 모았다
- 랜딩·요금·사례에 meta export: 제목을 브랜드가 아니라 **검색어**로 시작하게 바꿨다.
예전 제목("Web4Ai · AI 웹 빌더")에는 사람이 치는 말이 한 단어도 없었다.
랜딩에 Organization JSON-LD 추가 — 발행본에는 있는데 정작 랜딩엔 없었다
- src/lib/site.ts: 발행 호스트의 단일 출처. 모듈 최상위의 `window.location` 폴백을
전부 걷었다 — 서버 번들은 라우트를 한 파일로 묶어서 프리렌더 대상이 아닌 화면의
최상위 코드도 빌드 때 실행된다(실측: BuilderPage 에서 빌드가 죽었다)
- LoginPage: homePath 기본값 `/` → `/sites`. 예전엔 router.tsx 가 넘기던 값이라
라우트 모듈로 옮기면서 그대로 두면 로그인 후 랜딩으로 갔다
- nginx: SPA 폴백을 `/index.html` → `/__spa-fallback.html`. 프리렌더 뒤로
`/index.html` 은 **랜딩이 구워진 파일**이라, 그리로 넘기면 `/builder` 에 랜딩
HTML 이 내려가고 클라이언트가 다른 주소로 하이드레이트한다
- nginx/Dockerfile: 산출물이 `dist` → `build/client`. 경로가 어긋나면 COPY 가
조용히 빈 디렉토리를 만들고 컨테이너는 정상으로 뜬다
- site/seo/robots.ts: `/builder` `/login` `/signup` `/sites` `/account` Disallow.
이 경로들은 빈 SPA 폴백을 받는다 — 긁히면 호스트 전체에 저품질 신호가 쌓인다
검증: tsc·eslint·react-router build 통과.
랜딩 3,021B → 21,799B, 본문 1,278자, 링크 6개.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019fteiJNvAEbTnUKq8fSqoj
61 lines
2.4 KiB
Plaintext
61 lines
2.4 KiB
Plaintext
# 환경변수: 실제 값(.env)은 커밋하지 않는다. 템플릿(.env.example)만 커밋한다.
|
|
.env
|
|
.env.*
|
|
!.env.example
|
|
|
|
# 설정: 시크릿 포함이라 커밋하지 않는다. *.example 만 커밋한다.
|
|
*.toml
|
|
!*.toml.example
|
|
|
|
# Python 바이트코드/캐시 — 절대 커밋하지 않는다.
|
|
__pycache__/
|
|
*.py[cod]
|
|
*$py.class
|
|
.pytest_cache/
|
|
|
|
# 가상환경
|
|
.venv/
|
|
venv/
|
|
|
|
# 생성물(FastAPI OpenAPI 스펙). 필요 시 재생성한다.
|
|
solution/backend/openapi.json
|
|
|
|
# 발행 payload 는 solution/site/payloads/.gitignore 가 단일 출처다 — 여기에 겹쳐 쓰지 않는다.
|
|
|
|
# 정적 산출물 — 운영에서는 Docker named volume(site-out)에 있고, 로컬에서 직접 구우면
|
|
# 여기 생긴다. 어느 쪽이든 payload 로 다시 굽는 재생성물이라 git 이 관리할 대상이 아니다.
|
|
solution/site/out/
|
|
solution/site/dist/
|
|
# React Router 프레임워크 모드(solution/frontend)의 산출물
|
|
build/
|
|
.react-router/
|
|
admin/dist/
|
|
|
|
# nginx 설정: 서버마다 다르므로 실제 파일은 커밋하지 않는다. 템플릿만 커밋한다.
|
|
# ★ compose(docker-compose.yml:224)가 ./nginx/site.conf 를 bind mount 한다 —
|
|
# 파일이 없으면 Docker 가 그 자리에 **디렉토리를 만들어** nginx 가 설정 없이 뜬다.
|
|
# 클론 직후 반드시: cp nginx/site.conf.example nginx/site.conf
|
|
nginx/site.conf
|
|
!nginx/site.conf.example
|
|
|
|
# ── npm 워크스페이스(루트가 solution/{shared,front,site} + admin 을 묶는다) ──
|
|
node_modules/
|
|
dist/
|
|
.ssr-dist/
|
|
*.tsbuildinfo
|
|
|
|
# OS
|
|
.DS_Store
|
|
|
|
# ── 에이전트 지침은 커밋한다 ──────────────────────────────────────────────
|
|
# AGENTS.md / CLAUDE.md 는 팀과 모든 에이전트가 공유하는 규약이라 반드시 커밋한다.
|
|
# 커밋 안 하면 클론한 사람이 "배포 후 republish_all.py 필수" 같은 함정을 전달받지 못한다.
|
|
#
|
|
# ★ `!CLAUDE.md` 를 지우지 말 것. 이 머신의 ~/.gitignore_global 이 CLAUDE.md 를 전역으로
|
|
# 무시하고 있어서, 레포 쪽에서 명시적으로 되살리지 않으면 심볼릭 링크가 커밋되지 않는다.
|
|
# (전역 설정은 사람마다 다르므로 레포가 스스로를 지킨다.)
|
|
!CLAUDE.md
|
|
|
|
# 개인용 오버라이드는 레포가 아니라 ~/.claude/CLAUDE.md 나 .claude/settings.local.json 에 둔다.
|
|
.claude/settings.local.json
|