대충 만든 게 아니라, 설계로 만들었습니다
Duck Blog는 Next.js 16 + Vercel + Firebase로 구축된 개인 블로그입니다. 캐싱, SEO, 보안, 분석, 광고, 콘텐츠 관리까지 — 각 레이어가 왜 이렇게 설계되었는지 이 페이지에서 정리했습니다.
기술 스택
최신 스택을 쓰되, 안정성을 우선합니다. Next.js 16의 App Router와 Previous Caching Model, React 19, Tailwind CSS로 구성됩니다.
Next.js 16
App Router · Previous Caching Model · Turbopack. Metadata API로 SEO를 관리합니다.
React 19 + TypeScript
Server Components 기반. 타입으로 데이터 구조를 보장합니다.
Tailwind CSS 3.4
Vercel Minimal 디자인 토큰(ink/line/accent)으로 통일된 디자인 시스템.
Firebase Firestore
포스트·저자·댓글·좋아요를 Firestore로 관리하는 헤드리스 CMS.
next-mdx-remote
MDX + GFM + KaTeX + Prism. 수학 공식과 코드 하이라이팅을 지원합니다.
Vercel
main 브랜치 push 시 자동 배포. Edge 네트워크에서 정적 서빙.
요청 흐름
브라우저 요청이 Edge에서 콘텐츠까지 거쳐 다시 돌아오는 경로입니다. 대부분 요청은 캐시에서 바로 응답됩니다.
정적 페이지는 빌드 시 생성되어 Edge에서 영구 서빙되고, 콘텐츠 변경 시에만 온디맨드 무효화됩니다. 캐시 계층과 무효화 매트릭스는 아래 섹션에서 자세히 다룹니다.
캐싱 전략
원칙은 하나입니다: 영구 캐시 + 온디맨드 무효화. 시간 기반 재검증을 쓰지 않고, 콘텐츠가 실제로 바뀌는 순간에만 캐시를 갱신합니다.
React Request Cache
cache() — 단일 렌더 패스 내 동일 호출 중복 제거.
Data Cache
unstable_cache — 요청 간 데이터 캐싱, tag 기반 무효화.
Full Route Cache
HTML + RSC Payload 서버 캐싱, path 기반 무효화.
| CRUD 작업 | Tag 무효화 | Path 무효화 |
|---|---|---|
| Post Create | posts-all · tags-* | / · /blog · /blog/{cat} · 각 tag 페이지 |
| Post Update | posts-all · post-{slug} · tags-* | 위 + 포스트 페이지 · 카테고리/slug 변경 시 이전 경로 |
| Post Delete | posts-all · post-{slug} · tags-* | 위와 동일 |
| Author Update | author-{slug} | / · /about |
모든 무효화는 fire-and-forget(try/catch)이라 재검증 실패가 CRUD 응답을 막지 않습니다. 전체 매트릭스는 docs/caching-strategy.md에 있습니다.
SEO
2026-10 SEO 오딧에서 발견한 12개 문제를 반영한 최종 구성입니다. 타이틀, 언어, canonical, sitemap, 구조화 데이터까지 Metadata API로 관리합니다.
서픽스 없는 타이틀
template: '%s' — 포스트 타이틀이 검색 결과에 그대로 노출됩니다.
로케일별 html lang
LocaleLangSync가 pathname 기준으로 ko/en/ja를 동적으로 설정합니다.
페이지별 self canonical
root 전역 canonical 제거 — 각 페이지가 자기 URL을 canonical로 냅니다.
다국어 연결
홈 3개 라우트 + 포스트별 ko/en/ja + x-default를 sitemap과 함께 출력합니다.
구조화 데이터
포스트: BlogPosting + BreadcrumbList, 홈: Organization. script 태그로 렌더링합니다.
실시간 인덱싱
포스트 저장/상태 변경 시 IndexNow에 URL을 제출해 검색 반영을 앞당깁니다.
RSS(feed.xml), robots.txt, GSC/Naver 검증, OG Image 폴백까지 — 전체 구성은 docs/SEO.md에 문서화되어 있습니다.
호스팅 & 보안
Vercel Edge에서 보안 헤더를 일괄 적용합니다. CSP는 필요한 도메인만 허용하는 엄격한 설정입니다.
| 헤더 | 설정 | 의도 |
|---|---|---|
| Content-Security-Policy | GA · AdSense · Firebase 도메인만 허용 | 스크립트 주입 차단 |
| X-Frame-Options | DENY | 클릭재킹 방지 |
| X-Content-Type-Options | nosniff | MIME 스니핑 방지 |
| Strict-Transport-Security | max-age + preload | HTTPS 강제 |
| Referrer-Policy | strict-origin-when-cross-origin | 리퍼러 정보 최소화 |
| Permissions-Policy | 불필요한 브라우저 기능 비활성 | 표면 축소 |
ads.txt는 빌드 시 env에서 자동 생성되고, GSC HTML 검증 파일과 IndexNow 키 파일은 rewrite로 API 라우트에 연결됩니다.
분석 & 광고
트래킹과 수익화는 렌더링을 해치지 않는 방식으로 설계됩니다. 둘 다 env 기반이라 미설정 시 완전히 비활성화됩니다.
Google Analytics
gtag.js를 lazyOnload로 로드해 렌더링 블로킹을 방지합니다. logEvent() 헬퍼로 이벤트를 추적합니다.
Google AdSense
디스플레이(6079435296) + 인아티클(4907054773) 슬롯. 1초 지연 push로 AdBlocker 대응, CSP에 광고 도메인만 허용합니다.
콘텐츠 관리 (Firebase CMS)
리포지토리에 콘텐츠를 두지 않고 Firestore로 관리합니다. MDX를 Firestore 문서에 저장해 코드 배포 없이 글을 발행합니다.
posts 컬렉션
status: published / draft / deleted. MDX 콘텐츠 + 번역(translations) 필드.
이중 인증
클라이언트는 Firebase Auth, 서버 API는 admin SDK의 verifyIdToken으로 검증합니다.
/admin
포스트 CRUD, MDX 에디터, 아이디어, 댓글, 참여도, 프로필 관리.
댓글
비밀번호 해싱 + HTML 이스케이프 + 유효성 검증으로 스팸·XSS 대응.
좋아요
post-engagement 컬렉션, FieldValue.increment으로 원자적 증가.
vitest
rateLimit, comments, engagement, 캐싱, 문서 구조 — property 테스트 포함.
다국어 (i18n)
ko(기본) / en / jp 세 언어를 로케일별 라우트로 제공합니다. 번역이 없는 콘텐츠는 해당 언어에서 숨겨집니다.
| 콘텐츠 | ko | en | jp | 전략 |
|---|---|---|---|---|
| 홈 | ✓ | ✓ | ✓ | 로케일별 라우트, 번역 있는 글만 표시 |
| 포스트 | ✓ | 번역 시 | 번역 시 | translations 필드, content은 ko 원문 fallback |
| About | ✓ | — | — | ko 전용 (번역 없으면 숨김) |
| Projects | ✓ | — | — | ko 전용 |
사이트 구조
페이지 구성과 각 페이지의 역할을 정리했습니다.
| Route | 역할 | 캐싱 |
|---|---|---|
| / | Hero + 최신 포스트 + 소개 | Static (revalidate: false) |
| /blog | 전체 포스트, 태그 필터 + 무한 스크롤 | Static |
| /blog/[category]/[...slug] | 포스트 상세 (MDX 렌더링) | Static + generateStaticParams |
| /about | 개발자 소개 (로컬 MDX) | Static |
| /projects | 프로젝트 카드 | Static |
| /search | 실시간 검색 (client) | Shell only |
| /admin/* | 콘텐츠 관리 (인증 필요) | Shell only |
더 깊은 내용은 레포지토리에서
이 페이지는 요약본입니다. 캐싱 매트릭스, SEO 운영 체크리스트, use cache 마이그레이션 가이드까지 — 전체 문서는 GitHub의 docs/ 폴더에 있습니다.