Sveltekit Blog Engine
방명록
로그인
Read in:
English日本語한국어

SKBE v1.0.0.15 업데이트: D1 스냅샷 엔진, 스토리지 마이그레이션 및 SEO 최적화

devlog • 2026-09-20 PM3:29:56 (GMT+9) • 👀 1 •
#SvelteKit#Cloudflare#D1#성능최적화#SEO#스토리지

1. 시작하며: 운영 중 도출된 추가 개선 과제들

SvelteKit Blog Engine(SKBE) v1.0.0.11 ~ v1.0.0.14를 거치며 멀티 슬롯 디자인 시스템과 레이아웃 이동(CLS 0.000) 문제를 안정화한 이후, 실제 환경에서 글을 작성하고 운영해보면서 몇 가지 추가적인 비효율과 불편한 점들이 확인되었습니다.

이 프로젝트는 Cloudflare 무료 티어(Pages, D1 SQLite, KV)를 기반으로 운영 비용을 들이지 않는 구조를 지향하고 있기 때문에, 사소한 쿼리 낭비나 운영상의 번거로움도 가능한 한 줄여두는 편이 장기적으로 유리합니다.

이번 v1.0.0.15에서는 주로 다음과 같은 부분들을 점검하고 보완했습니다:

  1. 사이드바 위젯의 반복 쿼리: 방문자가 페이지를 이동할 때마다 카테고리, 최근글, 인기글을 매번 D1에 조회하던 구조 개선
  2. Workers 호출량 절감 및 이미지 서빙 최적화: 본문 내 프록시(/images/...) 주소를 R2/Supabase 직렬 엔드포인트로 일괄 전환하여 Cloudflare Workers 일일 무료 한도(100,000회) 소진 방어
  3. 레이아웃 렌더러의 마크업 정비: 검색엔진 봇이 본문과 보조 영역을 쉽게 구분할 수 있도록 시맨틱 태그 구조화
  4. 검색엔진 메타데이터 보완 및 태그 noindex 동적 제어: 사이트맵과 Open Graph 규격을 다듬고, 태그 페이지 색인 여부를 재배포 없이 어드민에서 켜고 끌 수 있는 설정 추가

2. D1 읽기 쿼리 절감: 사이드바 스냅샷 도입

1) 문제 상황

블로그 레이아웃(+layout.server.ts)에서는 어느 페이지를 열람하든 사이드바에 표시할 카테고리 목록, 최근 글, 인기 글 데이터를 준비해야 합니다.

기존에는 페이지 요청이 들어올 때마다 D1 데이터베이스에 이 세 가지 쿼리를 매번 실행했습니다. 방문자 수가 늘어날수록 D1 읽기 쿼리가 불필요하게 소모되었고, 엣지와 DB 간의 왕복 시간도 누적되었습니다.

2) 수정 내용: sidebar_snapshot 기반 정적 서빙

packages/shared/src/utils/snapshot.ts를 추가하여, 카테고리/최근글/인기글 데이터를 미리 묶어 blog_settings 테이블의 sidebar_snapshot 단일 필드에 캐싱해 두는 구조로 바꾸었습니다.

// apps/blog/src/routes/+layout.server.ts
const sidebarSnapshot = safeParse(settings?.sidebar_snapshot);
const hasSnapshot = sidebarSnapshot && typeof sidebarSnapshot === 'object' && sidebarSnapshot.recentPosts;

if (hasSnapshot) {
    // 스냅샷이 있으면 DB 복합 쿼리 없이 즉시 데이터 반환
    categories = sidebarSnapshot.categories?.[currentLang] || sidebarSnapshot.categories?.[defaultLang] || [];
    recentPosts = rLimit ? rawRecent.slice(0, rLimit) : rawRecent;
    popularPosts = pLimit ? rawPopular.slice(0, pLimit) : rawPopular;
    
    // 변동이 잦은 태그/댓글 위젯만 필요 시 가볍게 조회
} else {
    // 스냅샷이 없으면 1회 조회 후 백그라운드에서 스냅샷 생성
    generateSidebarSnapshot(rawD1).catch(err => console.error('[Snapshot Background]', err));
}
  • 결과: 일반 방문자가 글을 탐색할 때는 사이드바용 D1 쿼리가 발생하지 않고 바로 렌더링됩니다.
  • 글 작성이나 수정 시에는 백그라운드 비동기 작업으로 스냅샷을 갱신하므로 사용자 응답 속도에 영향을 주지 않습니다.

3. Cloudflare Workers 호출량 절감: 본문 이미지 URL 동기화 도구

1) 배경: 이미지 프록시 서빙의 한계와 스토리지 선택의 딜레마

SKBE는 ImageKit, Cloudflare R2, Supabase Storage, KV 등 다양한 미디어 스토리지를 지원합니다.

기술적/구조적 관점만 본다면 같은 Cloudflare 생태계에 대역폭(Egress) 무료 혜택이 있는 Cloudflare R2가 가장 적합합니다. 하지만 R2를 활성화하려면 Cloudflare 계정에 신용카드 등록이 필수입니다. R2는 대역폭 비용이 무료이지만, 저장 용량(10GB)이나 요청 한도를 넘길 경우 등록된 카드에서 자동으로 후불 결제되는 구조입니다.

반면 ImageKit은 신용카드 등록 없이도 무료 플랜을 사용할 수 있습니다. **미디어 저장 공간 3GB와 매월 20GB의 무료 글로벌 CDN 대역폭(2026년 9월 작성일 기준)**을 제공하며, 한도를 소진하더라도 카드로 자동 결제되지 않고 서비스만 일시 정지되므로 과금 위험이 전혀 없습니다. (※ 클라우드 및 스토리지 서비스의 무료 티어 정책은 제공사의 사정에 따라 향후 변동될 수 있습니다)

따라서 개인적으로는 우선 신용카드 등록 없이 안전한 ImageKit으로 시작하고, 이후 블로그가 성장하여 무료 한도(저장 공간 3GB 또는 월 20GB 대역폭)에 다다르게 되었을 때 Cloudflare R2로 이전하는 방식을 가장 추천합니다. 이번에 구현한 본문 이미지 URL 동기화 도구가 바로 이러한 스토리지 전환 시점에 기존 글들의 이미지 주소를 버튼 클릭 한 번으로 손쉽게 옮겨주는 역할을 합니다.

다만 어떤 스토리지를 사용하든, 초기 구현의 단순함을 위해 모든 이미지를 자체 엔드포인트(/images/...)를 거치는 프록시 방식으로 서빙했을 때 큰 문제가 발생했습니다. 방문자가 이미지가 여러 개 포함된 글 하나를 열람할 때마다 이미지 수만큼 Cloudflare Worker가 매번 실행되어 일일 무료 요청 한도(100,000회)를 빠르게 잠식하는 현상이었습니다.

이를 방지하기 위해 ImageKit이나 R2 직접 도메인, Supabase 직접 URL 서빙 모드를 도입하여 Worker를 거치지 않고 CDN/스토리지가 직접 트래픽을 처리하도록 개선했으나, 또 다른 현실적인 문제가 남았습니다. 이전에 작성된 기존 포스트 본문(<p><img src="/images/..."></p>) 내의 이미지 주소는 여전히 프록시 경로로 박혀 있어, 직렬 서빙 모드로 바꾸더라도 기존 글을 읽을 때는 계속 불필요한 Worker 요청이 발생하는 것이었습니다.

스토리지 변경이나 백업 복원 시에도 이전 주소가 그대로 남아 이미지가 깨지거나 불필요한 프록시 호출이 이어졌습니다.

⚠️ 중요 제약사항: 기본 Cloudflare KV 사용 시 URL 수정 불가
Cloudflare KV는 퍼블릭 직접 링크(Public Direct URL)를 지원하지 않습니다. 따라서 기본 KV 스토리지를 사용하는 상태에서는 직렬 서빙 전환이나 본문 URL 수정을 적용할 수 없으며, 무조건 도메인 프록시(/images/...) 방식으로만 동작합니다.
본문 이미지 URL 동기화 도구를 통해 Workers 호출량을 줄이려면 ImageKit(추천)이나 R2, Supabase와 같은 객체 스토리지로 전환해야 합니다.

2) 수정 내용: 본문 이미지 URL 일괄 마이그레이션 도구

기존 글들의 이미지 요청이 Worker를 우회하여 최적의 직렬 서빙 경로를 탈 수 있도록 URL 변환 도구를 구축했습니다:

  • URL 마이그레이션 엔드포인트 추가 (apps/admin/src/routes/api/media/migrate-urls/+server.ts):
    발행된 모든 포스트의 본문을 순회하며 프록시 경로(/images/...)나 이전 스토리지 패턴의 URL을 정규식으로 감지하고, 현재 활성화된 스토리지(R2 직접 도메인, Supabase 직접 서빙 등)의 엔드포인트 주소로 일괄 치환하여 저장합니다.

  • 어드민 도구 UI 배치 (apps/admin/src/routes/media/+page.svelte):
    스토리지 설정 탭 하단에 "포스트 이미지 URL 동기화 도구"를 배치하여, 저장소 서빙 모드를 전환한 후 버튼 클릭 한 번으로 모든 글의 이미지 URL을 즉시 직렬 경로로 일괄 갱신할 수 있게 했습니다.

  • 백업 복원 연동:
    백업 파일을 복원할 때도 "본문 이미지 URL 자동 마이그레이션" 옵션을 제공하여, 복원과 동시에 현재 블로그 환경의 직렬 서빙 주소로 자동 재구성되도록 보완했습니다.

  • 효과: 기존 글 열람 시 발생하던 이미지 프록시용 Workers 호출이 사라지고, 방문자는 스토리지/CDN에서 이미지를 직접 내려받아 Workers 일일 무료 한도를 안전하게 방어할 수 있게 되었습니다.

3) ⚠️ 안전한 마이그레이션을 위한 권장 수칙

본문 내 이미지 URL을 일괄 치환하는 작업은 데이터베이스의 글 내용을 직접 수정하는 과정이므로, 현실적으로 모든 환경과 예외 상황에 완벽히 대비되어 있다고 단정할 수는 없습니다. 따라서 마이그레이션을 진행할 때는 반드시 아래 수칙을 지키는 것을 권장합니다:

  1. 마이그레이션 전 전체 백업 필수:
    마이그레이션을 시도하기 전에 어드민의 백업 기능을 이용하여 D1 데이터베이스와 미디어 파일 전체를 반드시 로컬에 백업해 두어야 합니다.
  2. 원복(Rollback) 대비:
    혹시 마이그레이션 도중 오류가 발생하거나 주소가 잘못 매핑되더라도 즉시 원상태로 복원할 수 있도록, 기존 이미지 서버의 데이터를 미리 삭제하지 말고 그대로 둔 상태에서 진행해야 합니다.
  3. 테스트용 복사 블로그 검증 (강력 추천):
    가장 추천하는 안전한 방법은 백업받은 데이터로 테스트용 복사 블로그를 별도로 하나 개설하여 먼저 마이그레이션을 테스트해보는 것입니다. 테스트 환경에서 본문 이미지들이 깨짐 없이 정상 출력되는 것을 직접 확인한 뒤 본 블로그에 적용하는 것을 강력히 권장합니다.

4. 레이아웃 렌더러 시맨틱 태그 정비

기존 LayoutRenderer.svelte는 컬럼과 위젯 요소를 대부분 div 태그로 구성하고 있었습니다. 화면 표시는 정상이었으나, 검색엔진 크롤러 입장에서 본문 영역과 보조 영역(사이드바)을 보다 명확히 구분할 수 있도록 HTML5 시맨틱 태그를 적용했습니다:

  • 본문 위젯(post_content)이 포함되지 않은 컬럼은 <aside class="layout-column sidebar-column">으로 감싸 보조 콘텐츠임을 명시했습니다.
  • 본문이 포함된 영역은 <div class="layout-column main-column">으로 분리했습니다.
  • 개별 위젯 블록에는 <section class="widget-item ..."> 태그와 적절한 <h3> 타이틀을 부여했습니다.

5. 검색엔진(SEO) 메타데이터 보완

배포된 사이트의 실제 크롤링 상태를 점검하면서 발견된 자잘한 규격 누락 사항들을 함께 손보았습니다:

  1. 사이트맵 홈페이지 <lastmod> 누락 보완 (sitemap.xml/+server.ts):
    개별 글 URL에는 수정일이 있었으나 루트(/) 엔트리에는 <lastmod>가 빠져 있어 최신 포스트의 수정일을 반영하도록 추가했습니다.
  2. 사이드바 태그 링크 인코딩 (TagCloudWidget.svelte):
    공백이 포함된 태그가 raw 문자열로 링크되던 부분에 encodeURIComponent를 적용했습니다.
  3. 홈페이지 og:image 폴백 ([[lang=lang]]/+page.server.ts):
    어드민에서 별도의 로고를 지정하지 않았을 때 SNS 공유 시 썸네일이 비어 있던 문제를, 최신 글의 대표 이미지(lcpImage)로 폴백되도록 처리했습니다.
  4. og:locale 다국어 표준화 (SeoHead.svelte):
    언어 코드가 en, ja로 출력되던 것을 표준 Open Graph 규격인 en_US, ja_JP로 변환하여 출력하도록 분기를 추가했습니다.

6. 태그 noindex 어드민 제어 및 다국어 딕셔너리 반영

1) 배경

블로그 개설 초기에는 글 수가 적어 태그 페이지마다 비슷한 글 목록이 중복 노출되기 쉽습니다. 이로 인한 검색엔진의 중복 콘텐츠 페널티를 피하기 위해 태그 페이지는 기본적으로 noindex 처리를 해두는 편이 안전합니다.

다만 블로그가 커지고 태그별로 글이 충분히 쌓였을 때 색인을 허용하고 싶다면, 기존에는 코드를 직접 고쳐 재배포해야 했습니다.

2) 수정 내용

  • 어드민 사이트 설정에 토글 추가 (apps/admin/src/routes/settings/+page.svelte):
    "태그 페이지 검색엔진 색인 차단 (noindex)" 항목을 추가하여, 켜면 noindex(차단), 끄면 index(허용)가 되도록 구성했습니다. 기본값은 ON입니다.
  • 블로그 실시간 연동 (apps/blog/src/routes/[[lang=lang]]/tags/[tag]/+page.server.ts):
    하드코딩되어 있던 noindex: true를 어드민 설정값(settings?.tag_page_noindex !== 'false')으로 교체하여, 재배포 없이 설정 저장만으로 동작이 바뀌도록 했습니다.
  • i18n 사전 등록:
    해당 항목의 제목과 설명을 packages/shared/src/i18n/index.ts의 공통 딕셔너리에 한국어, 영어, 일본어로 각각 등록했습니다.

7. 마치며

이번 v1.0.0.15 작업은 겉으로 드러나는 화려한 기능을 추가하기보다는, 실제로 서비스를 운영하면서 마주치는 자잘한 비효율과 관리상의 번거로움을 줄이는 데 초점을 맞추었습니다.

반복되는 DB 조회를 줄이고, 스토리지 변경 시의 손이 덜 가도록 정리하고, 검색엔진 규격을 조금 더 깔끔하게 다듬는 것만으로도 운영 부담이 한결 가벼워지는 것을 느낍니다. 앞으로도 실제 운영 관점에서 필요한 부분들을 차분히 다듬어 나갈 생각입니다.

소셜 네트워크로 글 공유하기

댓글 0개

댓글을 작성하려면 로그인이 필요합니다.

댓글을 불러오는 중...

정말 삭제하시겠습니까?

정말 이 댓글을 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.

인기 태그

#API Token#CLI#Cloudflare#CloudflarePages#D1#Deploy#FAQ#Guide#Hreflang#IP설정
1 / 9

인기 포스트

  • CLI 환경에서 Cloudflare Wrangler 로그인 및 API 토큰 설정 상세 설명서
    2026-07-06 PM7:49:33 (GMT+9)
  • [개발 일지] 2026-07-08
    2026-07-08 PM11:00:14 (GMT+9)
  • 웹 폰트 설정법 및 로딩 성능 최적화 매뉴얼
    2026-07-07 PM3:36:18 (GMT+9)

인기 태그

#API Token#CLI#Cloudflare#CloudflarePages#D1#Deploy#FAQ#Guide#Hreflang#IP설정
1 / 9

후원

Github

카테고리 목록

  • 개발로그 (9)
  • 관리자 가이드 (5)
  • 사용자 가이드 (3)
  • 상세 메뉴얼 (4)
  • 통합 가이드 (3)

최신 포스트

  • SKBE v1.0.1.0 업데이트: 슬롯별 디자인 독립 프리셋, 반응형 레이아웃 및 다국어 최적화
    2026-09-21 AM8:18:38 (GMT+9)
  • SKBE v1.0.0.15 업데이트: D1 스냅샷 엔진, 스토리지 마이그레이션 및 SEO 최적화
    2026-09-20 PM3:29:56 (GMT+9)
  • SvelteKit Blog Engine 고도화 기록: 멀티 슬롯 디자인, D1 캐시 최적화, SEO 정규화 (v1.0.0.11 ~ v1.0.0.13)
    2026-09-08 AM11:58:10 (GMT+9)

인기 포스트

  • CLI 환경에서 Cloudflare Wrangler 로그인 및 API 토큰 설정 상세 설명서
    2026-07-06 PM7:49:33 (GMT+9)
  • [개발 일지] 2026-07-08
    2026-07-08 PM11:00:14 (GMT+9)
  • 웹 폰트 설정법 및 로딩 성능 최적화 매뉴얼
    2026-07-07 PM3:36:18 (GMT+9)

프로젝트 후원하기 ☕

Github Link

소개개인정보 처리방침문의이용약관

© 2026 스벨트킷 블로그 엔진 · Powered by Sveltekit Blog Engine on Svelte 5

RSS SKBE v1.0.1.1 디자인 공유