// blog

정적 콘텐츠와 동적 콘텐츠를 나누는 기준

2025.11.19 발행 · 수정 2026.08.02

혼란스러운 초기 상태

davhave.com을 만들 때, 가장 먼저 마주친 질문:

"블로그를 정적 파일로 관리할까, 데이터베이스로 할까?"

당시 나는:

각각의 장단점이 명확하지 않았다. 많은 사람들이 "정적 사이트는 빠르다"고 했지만, 그게 정말 사실일까?

정의: 정적 vs 동적이란?

정적 콘텐츠

파일 시스템 (git, 로컬 폴더)
  ├─ index.html
  ├─ blog/
  │  ├─ post-1.html
  │  ├─ post-2.html
  │  └─ post-3.html
  ├─ style.css
  └─ main.js

배포 시: 이 폴더를 통째로 업로드
조회 시: 파일을 그냥 반환 (DB 쿼리 없음)

동적 콘텐츠

데이터베이스
  ├─ posts 테이블 (id, title, slug, content)
  ├─ tags 테이블
  └─ comments 테이블

배포 시: 코드만 업로드 (데이터는 DB에 있음)
조회 시: SQL 쿼리 → 데이터 가져오기 → HTML 생성

선택 기준: 6가지 질문

1. 얼마나 자주 변한다?

변경 빈도 추천 이유
월 1회 이하 정적 수정 후 빌드 배포 면역
주 1회 정적/동적 선택 둘 다 가능
일 1회 이상 동적 재배포 오버헤드
실시간 동적 동적만 가능

davhave의 경우:

2. 사용자 상호작용이 있는가?

정적: 댓글, 좋아요, 실시간 업데이트 불가능
      (추가 JS 라이브러리 필요)

동적: 댓글 저장, 추천 수 계산, 실시간 알림 가능
     (DB와 상호작용)

davhave에서는:

3. 검색 및 필터링이 필요한가?

정적: 블로그 목록 페이지를 미리 생성해야 함
     예: /blog.html, /blog/tag/javascript.html
     
     문제: 태그가 100개면 100개 페이지 생성?

동적: 쿼리 시점에 필터링
     예: /blog?tag=javascript (1개 API)

davhave의 선택:

4. 버전 관리와 협업이 필요한가?

정적: git으로 모든 변경 추적
     장점: 완벽한 히스토리, 코드 리뷰 가능
     단점: 포스트 수 많아지면 repo 무거워짐

동적: DB에 저장, 필요시 별도 버전 관리
     장점: 데이터와 코드 분리
     단점: DB 마이그레이션 복잡

5. SEO가 중요한가?

정적: 이미 HTML로 완성 → 크롤러가 읽기 쉬움

동적: SSR (Server-Side Rendering) 필수
     예: Cloudflare Workers에서 HTML 생성

davhave는 블로그/교육이 주요 SEO 대상이므로 동적 SSR 필수.

6. 성능과 비용

정적:
- 서버 리소스 거의 안 씀 (저가 호스팅 가능)
- 빌드 시간 필요 (포스트 100개 = 몇십 초)
- 캐싱 쉬움 (모든 게 이미 최종 파일)

동적:
- 요청마다 DB 쿼리 (레이턴시 증가 가능)
- 캐싱 필수 (캐시 무효화 복잡)
- 서버 비용 증가 (계산이 필요하므로)

davhave의 최종 선택: 하이브리드

결론: "콘텐츠 타입별로 다르게"

정적 콘텐츠

홈페이지 (/)
├─ index.html (정적)
├─ style.css (정적)
├─ script.js (정적)

포트폴리오 정보
├─ src/lib/projects.js (코드에 하드코딩)
└─ /portfolio → 정적 페이지로 렌더링

이유:

동적 콘텐츠

블로그
├─ D1에서 데이터 조회
├─ Workers에서 SSR로 페이지 생성
└─ 엣지에서 1시간 캐싱

교육 콘텐츠
├─ D1에서 데이터 조회
├─ 카테고리/레슨 동적 생성
└─ 댓글/퀴즈 지원 (향후)

이유:

구현 패턴

정적 콘텐츠: 직접 파일 서빙

// src/worker.js
export default {
  async fetch(request, env) {
    const url = new URL(request.url);

    // 홈페이지는 정적 파일에서
    if (url.pathname === '/' || url.pathname === '/index.html') {
      return env.ASSETS.fetch(request);
    }

    // 포트폴리오도 정적
    if (url.pathname.startsWith('/portfolio')) {
      return env.ASSETS.fetch(request);
    }

    // ... 동적 라우트는 별도 처리
  }
};

동적 콘텐츠: DB + 캐싱

export async function renderBlogPage(slug, env) {
  // 1. 캐시 확인
  const cacheUrl = new Request(`https://blog-cache/${slug}`, {
    method: 'GET'
  });
  let response = await caches.default.match(cacheUrl);

  if (!response) {
    // 2. DB 쿼리
    const post = await env.DB.prepare(`
      SELECT * FROM posts WHERE slug = ?
    `).bind(slug).first();

    // 3. 페이지 렌더링
    const html = renderPostHTML(post);

    // 4. 캐싱
    response = new Response(html, {
      headers: {
        'Content-Type': 'text/html',
        'Cache-Control': 'public, max-age=3600'
      }
    });

    await caches.default.put(cacheUrl, response.clone());
  }

  return response;
}

마이그레이션: 정적 → 동적

포트폴리오가 관리하기 힘들어지면?

// 현재 (정적)
export const PROJECTS = [
  {
    id: 1,
    title: 'kconstrade',
    slug: 'kconstrade',
    // ...
  }
];

// ↓ 마이그레이션

// 미래 (동적)
async function getProjects() {
  return await env.DB.prepare(`
    SELECT * FROM projects WHERE published = true
  `).all();
}

코드 변경은 최소다.

성능 비교: 실제 측정

davhave.com의 실제 응답 시간:

콘텐츠 타입 응답 시간 캐시 적중 시
홈페이지 정적 5ms 1ms
블로그 동적 (캐시) 50ms 2ms
교육 동적 (캐시) 50ms 2ms
포트폴리오 정적 5ms 1ms

차이 없다. 왜? 캐싱이 있으니까.

결론: 맥락이 전부

정적/동적은 절대적인 판단이 아니라 상황 기반이다:

정적: "이 콘텐츠가 거의 안 바뀌고, 
       미리 만들 수 있고,
       DB 쿼리가 필요 없으면" 정적.

동적: "이 콘텐츠가 자주 바뀌거나,
       사용자 데이터와 상호작용하거나,
       실시간 검색이 필요하면" 동적.

davhave는 이 둘을 섞어서 최고의 성능과 유지보수성을 달성했다.

https://davhave.com/blog/static-dynamic-content