Logo
duck blog | 기술 블로그
Engineering Notes

대충 만든 게 아니라, 설계로 만들었습니다

Duck Blog는 Next.js 16 + Vercel + Firebase로 구축된 개인 블로그입니다. 캐싱, SEO, 보안, 분석, 광고, 콘텐츠 관리까지 — 각 레이어가 왜 이렇게 설계되었는지 이 페이지에서 정리했습니다.

01

기술 스택

최신 스택을 쓰되, 안정성을 우선합니다. Next.js 16의 App Router와 Previous Caching Model, React 19, Tailwind CSS로 구성됩니다.

Framework

Next.js 16

App Router · Previous Caching Model · Turbopack. Metadata API로 SEO를 관리합니다.

UI

React 19 + TypeScript

Server Components 기반. 타입으로 데이터 구조를 보장합니다.

Styling

Tailwind CSS 3.4

Vercel Minimal 디자인 토큰(ink/line/accent)으로 통일된 디자인 시스템.

Content

Firebase Firestore

포스트·저자·댓글·좋아요를 Firestore로 관리하는 헤드리스 CMS.

Rendering

next-mdx-remote

MDX + GFM + KaTeX + Prism. 수학 공식과 코드 하이라이팅을 지원합니다.

Deploy

Vercel

main 브랜치 push 시 자동 배포. Edge 네트워크에서 정적 서빙.

02

요청 흐름

브라우저 요청이 Edge에서 콘텐츠까지 거쳐 다시 돌아오는 경로입니다. 대부분 요청은 캐시에서 바로 응답됩니다.

Vercel Edge
CDN · 보안 헤더 · rewrite
Next.js 16
App Router · Turbopack
3-Layer Cache
Request → Data → Route
Firestore
posts · authors · engagement
Client (React 19)
RSC Payload · hydration
GA · AdSense
lazy load · CSP 허용 도메인
Admin
Firebase Auth · CRUD API

정적 페이지는 빌드 시 생성되어 Edge에서 영구 서빙되고, 콘텐츠 변경 시에만 온디맨드 무효화됩니다. 캐시 계층과 무효화 매트릭스는 아래 섹션에서 자세히 다룹니다.

03

캐싱 전략

원칙은 하나입니다: 영구 캐시 + 온디맨드 무효화. 시간 기반 재검증을 쓰지 않고, 콘텐츠가 실제로 바뀌는 순간에만 캐시를 갱신합니다.

Layer 1

React Request Cache

cache() — 단일 렌더 패스 내 동일 호출 중복 제거.

Layer 2

Data Cache

unstable_cache — 요청 간 데이터 캐싱, tag 기반 무효화.

Layer 3

Full Route Cache

HTML + RSC Payload 서버 캐싱, path 기반 무효화.

CRUD 작업Tag 무효화Path 무효화
Post Createposts-all · tags-*/ · /blog · /blog/{cat} · 각 tag 페이지
Post Updateposts-all · post-{slug} · tags-*위 + 포스트 페이지 · 카테고리/slug 변경 시 이전 경로
Post Deleteposts-all · post-{slug} · tags-*위와 동일
Author Updateauthor-{slug}/ · /about

모든 무효화는 fire-and-forget(try/catch)이라 재검증 실패가 CRUD 응답을 막지 않습니다. 전체 매트릭스는 docs/caching-strategy.md에 있습니다.

04

SEO

2026-10 SEO 오딧에서 발견한 12개 문제를 반영한 최종 구성입니다. 타이틀, 언어, canonical, sitemap, 구조화 데이터까지 Metadata API로 관리합니다.

Title

서픽스 없는 타이틀

template: '%s' — 포스트 타이틀이 검색 결과에 그대로 노출됩니다.

Lang

로케일별 html lang

LocaleLangSync가 pathname 기준으로 ko/en/ja를 동적으로 설정합니다.

Canonical

페이지별 self canonical

root 전역 canonical 제거 — 각 페이지가 자기 URL을 canonical로 냅니다.

hreflang

다국어 연결

홈 3개 라우트 + 포스트별 ko/en/ja + x-default를 sitemap과 함께 출력합니다.

JSON-LD

구조화 데이터

포스트: BlogPosting + BreadcrumbList, 홈: Organization. script 태그로 렌더링합니다.

IndexNow

실시간 인덱싱

포스트 저장/상태 변경 시 IndexNow에 URL을 제출해 검색 반영을 앞당깁니다.

RSS(feed.xml), robots.txt, GSC/Naver 검증, OG Image 폴백까지 — 전체 구성은 docs/SEO.md에 문서화되어 있습니다.

05

호스팅 & 보안

Vercel Edge에서 보안 헤더를 일괄 적용합니다. CSP는 필요한 도메인만 허용하는 엄격한 설정입니다.

헤더설정의도
Content-Security-PolicyGA · AdSense · Firebase 도메인만 허용스크립트 주입 차단
X-Frame-OptionsDENY클릭재킹 방지
X-Content-Type-OptionsnosniffMIME 스니핑 방지
Strict-Transport-Securitymax-age + preloadHTTPS 강제
Referrer-Policystrict-origin-when-cross-origin리퍼러 정보 최소화
Permissions-Policy불필요한 브라우저 기능 비활성표면 축소

ads.txt는 빌드 시 env에서 자동 생성되고, GSC HTML 검증 파일과 IndexNow 키 파일은 rewrite로 API 라우트에 연결됩니다.

06

분석 & 광고

트래킹과 수익화는 렌더링을 해치지 않는 방식으로 설계됩니다. 둘 다 env 기반이라 미설정 시 완전히 비활성화됩니다.

Analytics

Google Analytics

gtag.js를 lazyOnload로 로드해 렌더링 블로킹을 방지합니다. logEvent() 헬퍼로 이벤트를 추적합니다.

Ads

Google AdSense

디스플레이(6079435296) + 인아티클(4907054773) 슬롯. 1초 지연 push로 AdBlocker 대응, CSP에 광고 도메인만 허용합니다.

07

콘텐츠 관리 (Firebase CMS)

리포지토리에 콘텐츠를 두지 않고 Firestore로 관리합니다. MDX를 Firestore 문서에 저장해 코드 배포 없이 글을 발행합니다.

Posts

posts 컬렉션

status: published / draft / deleted. MDX 콘텐츠 + 번역(translations) 필드.

Auth

이중 인증

클라이언트는 Firebase Auth, 서버 API는 admin SDK의 verifyIdToken으로 검증합니다.

Admin

/admin

포스트 CRUD, MDX 에디터, 아이디어, 댓글, 참여도, 프로필 관리.

Comments

댓글

비밀번호 해싱 + HTML 이스케이프 + 유효성 검증으로 스팸·XSS 대응.

Engagement

좋아요

post-engagement 컬렉션, FieldValue.increment으로 원자적 증가.

Tests

vitest

rateLimit, comments, engagement, 캐싱, 문서 구조 — property 테스트 포함.

08

다국어 (i18n)

ko(기본) / en / jp 세 언어를 로케일별 라우트로 제공합니다. 번역이 없는 콘텐츠는 해당 언어에서 숨겨집니다.

콘텐츠koenjp전략
홈✓✓✓로케일별 라우트, 번역 있는 글만 표시
포스트✓번역 시번역 시translations 필드, content은 ko 원문 fallback
About✓——ko 전용 (번역 없으면 숨김)
Projects✓——ko 전용
09

사이트 구조

페이지 구성과 각 페이지의 역할을 정리했습니다.

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/ 폴더에 있습니다.