1. 시작하며: 운영 환경에서 발견된 개선 과제들
SvelteKit Blog Engine(SKBE) v1.0.0.10을 릴리즈한 이후, 실제 환경에서 서비스를 운영하고 테스트하면서 몇 가지 구조적인 병목과 보완점이 확인되었습니다.
특히 이 프로젝트는 Cloudflare Free Tier(Pages, D1 SQLite, KV) 환경에서 월 비용 0원($0)으로 운영되는 서버리스 아키텍처를 지향합니다. 따라서 트래픽이나 데이터가 늘어나더라도 무료 한도 내에서 안정적으로 서비스가 유지되도록 설계하는 것이 핵심이었습니다.
점검 과정에서 도출된 주요 과제는 다음과 같았습니다:
- D1 데이터베이스 쿼리 효율화: Cloudflare D1의 일일 쓰기 한도(100,000회)와 반복적인 읽기 쿼리를 어떻게 최소화할 것인가?
- 모바일 렌더링 안정성: JS 마운트 시점에 레이아웃을 분기하면서 발생하던 모바일 화면의 미세한 누적 레이아웃 이동(CLS)을 어떻게 없앨 것인가?
- 검색엔진(SEO) 및 AI 크롤러 규격 준수: 다국어 환경에서 canonical URL과 hreflang 사이의 모순을 해결하고, ChatGPT나 Perplexity 같은 최신 AI 검색 봇을 위한 표준 규격을 갖추었는가?
이 과제들을 해결하기 위해 v1.0.0.11부터 v1.0.0.13까지 순차적으로 진행했던 기술적 검토와 코드 수정 내역을 정리해 보았습니다.
2. [v1.0.0.11] 안정성 보완: 애드센스 렌더링 및 런타임 오류 대응
첫 번째 단계에서는 광고 노출과 관련된 레이아웃 문제 및 특정 파라미터 인입 시 발생하던 500 에러를 점검했습니다.
애드센스 데스크톱 공백 렌더링 방어
모바일에서는 광고가 정상 표시되었으나, 데스크톱의 특정 너비 구간에서 구글 애드센스 반응형 유닛이 높이 0px로 렌더링되며 공백으로 남는 현상이 있었습니다.
- 원인: CSS 그리드 트랙의 반응형 계산 시점과 애드센스 스크립트(
adsbygoogle.push)가 컨테이너 폭을 측정하는 시점 사이에 타이밍 차이가 존재했습니다. - 조치: 광고 래퍼에
min-height: 280px와 반응형 컨테이너 스타일을 지정하여, 스크립트 실행 전에도 안정적인 렌더링 영역이 확보되도록 구조를 보완했습니다.
특수문자 태그 및 다국어 라우팅 안정화
특수문자나 공백이 포함된 태그 페이지에 접근할 때 SvelteKit 라우트 매처와 D1 쿼리 바인딩 간 인코딩 불일치로 발생하던 500 에러를 수정했습니다.
3. [v1.0.0.12] 구조 개선: 디자인 유연성과 D1 성능 최적화
v1.0.0.12에서는 디자인 관리의 유연성을 높이고, Cloudflare D1의 읽기/쓰기 부하를 대폭 줄이는 데 집중했습니다.
1) 3-슬롯 멀티 디자인 시스템 (Multi-Slot Design Architecture)
기존의 일반적인 블로그 엔진(티스토리, 워드프레스, 고스트 등)에서 디자인을 개편하려면 꽤 큰 부담을 감수해야 했습니다. 새 테마를 적용하는 순간 기존 세팅이 덮어씌워지므로 코드를 백업해 두지 않으면 이전 디자인으로 되돌리기가 번거로웠고, 무엇보다 운영 중인 블로그에서 방문자에게 깨진 레이아웃을 노출하지 않고 실시간으로 새 디자인을 실험하기가 사실상 불가능했습니다.
SKBE v1.0.0.12에서는 이 문제를 구조적으로 해결하기 위해 **'스냅샷 기반 3-슬롯 디자인 아키텍처'**를 도입했습니다.
3개의 독립된 디자인 스냅샷 동시 유지:
- 어드민 디자인 에디터에서 **슬롯 1(기본 메인), 슬롯 2(리디자인 실험), 슬롯 3(다크/이벤트 테마)**을 각각 독립된 완결형 스냅샷으로 구성하고 저장할 수 있습니다.
- 새 디자인을 작업할 때 라이브 서비스 중인 메인 슬롯에는 아무런 영향이 없으며, 완성된 후 마우스 클릭 한 번으로 활성 슬롯을 교체하거나 언제든 1초 만에 이전 디자인으로 롤백할 수 있습니다.
운영 전략에 따른 3가지 노출 모드:
- 단일 슬롯 고정: 관리자가 지정한 슬롯만 라이브로 단독 서비스
- 세션 랜덤 로테이션: 방문자 세션마다 슬롯을 무작위로 교체 노출하여 디자인 A/B 테스트 효과 및 신선한 사용자 경험 제공
- 방문자 실시간 테마 스위처: 우측 하단 플로팅 버튼을 통해 방문자가 사이트 내에서 원하는 테마를 직접 즉시 전환
성능 낭비 없는 조건부 SSR 페이로드 설계:
- 테마가 3개나 존재한다고 해서 사이트가 무거워지면 안 됩니다.
- 방문자 테마 선택 모드가 꺼져 있을 때는 활성 슬롯 단 1개의 CSS/설정 데이터만 HTML에 번들링하고 보조 슬롯 데이터는 전송하지 않습니다.
- 방문자 선택 기능이 활성화되었을 때만 보조 슬롯 데이터를 조건부 패키징하도록
+layout.server.ts를 제어하여, 멀티 테마 시스템을 유지하면서도 초기 SSR 로딩 속도와 HTML 용량은 단일 테마와 완전히 동일하게 가볍게 유지했습니다.
2) Cloudflare D1 캐시 레이어 및 조회수 버퍼링
Cloudflare D1의 무료 티어를 효율적으로 활용하기 위해 인메모리 캐싱과 배치 처리 로직을 도입했습니다.
- 인메모리 TTL 캐시 (
cache.ts): 블로그 기본 설정, 레이아웃, 태그 목록처럼 변경 빈도가 낮은 데이터에 60초 TTL 인메모리 캐시를 적용했습니다. 그 결과 페이지 진입 시 발생하는 D1 읽기량(rows_read)이 98% 이상 감소했습니다. - 조회수 배치 버퍼링 (
viewBuffer.ts): 게시글 조회 시마다 매번 D1에UPDATE쿼리를 보내지 않고, 10건이 모이거나 30초가 경과했을 때 한 번에 묶어서 DB에 반영하는 버퍼를 구축했습니다. 사용자 화면에는 버퍼에 대기 중인 카운트를 즉시 합산하여 반환하므로 화면 표시 지연은 발생하지 않습니다. - 역정규화 카운터 컬럼 적용: 글 목록 조회 시 무거운 집계(COUNT/JOIN) 연산을 방지하기 위해 카테고리에
post_count, 게시글에view_count컬럼을 두고 글 작성·수정·삭제 시점에 자동으로 수치가 동기화되도록 정리했습니다.
3) 모바일 Core Web Vitals 및 렌더링 최적화
- 모바일 누적 레이아웃 이동 방어 (CLS 0.000): 기존에 JS 마운트 후 화면 폭을 판단하여 뷰를 전환하던 방식을 순수 CSS 미디어 쿼리(
@media (max-width: 768px))로 전면 개편했습니다. 브라우저가 첫 번째 프레임(0.001초)부터 모바일 레이아웃을 확정하므로, CLS 수치가 1.0(불량)에서 0.000(우수)으로 정돈되었습니다. - 다국어 사전 평탄화 (HTML 70% 축소): 블로그 프론트엔드에 불필요했던 어드민 전용 번역 키를 제외하고, 현재 접속 언어 단일 문자열로 사전을 평탄화(Flattening)했습니다. 사전 페이로드가 179 KB에서 9.19 KB로 94.87% 줄어들며, 전체 HTML 문서 크기도 70% 이상 가벼워졌습니다.
- 웹폰트 비차단 Preload:
<head>내 웹폰트 호출을rel="preload"+onload교체 방식으로 구성하여 렌더링 차단 현상을 해소하고 First Contentful Paint(FCP) 속도를 개선했습니다.
4. [v1.0.0.13] 웹 표준 완성: SEO 정규화 및 llms.txt 엔드포인트
세 번째 단계에서는 검색엔진 인덱싱의 모순을 바로잡고, 최신 AI 봇이 참조할 수 있는 표준 엔드포인트를 구축했습니다.
1) SEO 캐노니컬(Canonical) URL 정규화
다국어 라우팅 환경([[lang=lang]])에서 기본 언어로 접속했을 때 canonical 메타 태그에 기본 언어 경로(예: /ko)가 포함되어 sitemap.xml 및 hreflang 태그와 충돌하는 현상을 해결했습니다.
- 기본 언어 접두사 자동 제거: 시스템에 기본 언어로 설정된 언어로 접속할 경우, 홈(
/), 카테고리(/tech), CMS 페이지 모두 canonical URL에서 기본 언어 접두사를 제거하여 사이트맵 표준 경로와 1:1로 일치시켰습니다. - 절대 경로 Fallback 보장: 상세 페이지와 방명록에서
siteUrl미설정 시 상대 경로가 생성되던 예외를 점검하고, 항상url.origin을 참조하도록 폴백을 두어 모든 환경에서 유효한 절대 경로가 출력되도록 보완했습니다.
2) 조회수 쿠키 중복 방지 및 RFC 6265 안전 해시 키
- 10분 윈도우 중복 방지: 브라우저
sessionStorage외에 서버 측 쿠키 검증 로직을 추가하여, 10분 이내 새로고침이나 탭 복제로 인한 비정상적인 조회수 증가를 방어했습니다. - 한글 슬러그 500 에러 해결: 한글/특수문자 슬러그가 쿠키 이름으로 들어갈 때 Node/SvelteKit에서 헤더 규격 위반(
TypeError: argument name is invalid)으로 500 에러가 발생하던 문제를 해결했습니다. 슬러그를 RFC 6265 호환 영숫자 해시(skbe_v_${hash})로 변환하여 안전하게 쿠키 키를 발급합니다.
3) 다국어 지원 동적 llms.txt 엔드포인트 구축
ChatGPT Search, Perplexity 등 대형 언어 모델 기반 검색 봇의 크롤링을 지원하기 위해 llmstxt.org 규격의 엔드포인트를 추가했습니다.
- 다국어 경로 분기:
/llms.txt(기본 언어),/en/llms.txt(영어),/ja/llms.txt(일본어)를 제공합니다. - 마크다운 구조화: 사이트 소개, 활성 카테고리, 최근 공개 글 30건(요약문 포함), CMS 페이지, 사이트맵/RSS 피드 링크를 표준 마크다운 형식으로 동적 생성합니다.
- Cloudflare CDN 엣지 캐싱: 10분 캐시(
s-maxage=600)를 적용하여 Google PageSpeed Insights 모바일 검사 시 발생하던Fetch of llms.txt timed out경고를 해소했습니다.
5. 주요 개선 지표 요약
3개 버전에 걸쳐 적용된 주요 개선 결과는 다음과 같습니다:
| 점검 항목 | 개선 전 | 개선 후 | 비고 |
|---|---|---|---|
| D1 읽기 쿼리 | 페이지 로드 시 매번 DB 조회 | 60초 인메모리 TTL 캐시 적용 | 읽기량 98% 이상 절감 |
| D1 쓰기 빈도 | 조회수 발생 시 즉시 DB Write | 10건 / 30초 배치 버퍼링 | 일일 무료 쓰기 한도 보호 |
| 모바일 CLS | 1.0 (JS 마운트 후 레이아웃 분기) | 0.000 (순수 CSS 미디어쿼리) | 초기 로딩 흔들림 제거 |
| 다국어 사전 용량 | 179 KB (전체 키 번들링) | 9.19 KB (단일 언어 평탄화) | 94.87% 페이로드 감축 |
| SSR HTML 크기 | 약 80~100 KB | 약 20~25 KB | 70% 이상 경량화 |
| SEO Canonical | 기본 언어 접두사 불일치 발생 | 사이트맵 및 hreflang과 1:1 일치 | 중복 색인 리스크 차단 |
| AI 검색 지원 | 미지원 (Lighthouse 경고) | 다국어 llms.txt 표준 지원 | 차세대 검색 봇 인용 대응 |
6. 정리하며: 개발 비하인드와 Hub 운영 안내
11, 12, 13버전을 한 번에 묶어 전하게 된 이유
사실 v1.0.0.11은 자잘한 버그 픽스 위주라 단독 포스팅으로 다루기엔 내용이 다소 가벼워, 메이저급 변경이 포함된 v1.0.0.12와 함께 정리하려고 준비 중이었습니다.
그런데 12버전 작업 직후, 한글 슬러그에서의 조회수 500 에러와 다국어 canonical URL의 미세한 불일치라는 치명적인 결함(v1.0.0.13)을 추가로 발견하게 되었습니다. 실제 서비스를 이용하고 클론해 가시는 분들을 위해서는 글을 작성하는 것보다 코드의 결함을 잡고 깃허브에 최우선으로 패치 배포하는 것이 급선무라 판단했습니다.
긴급 수정과 릴리즈 배포를 먼저 마무리 짓고 안정성을 확보한 뒤 글을 정리하다 보니, 결과적으로 11부터 13까지 세 버전을 아우르는 긴 호흡의 개발로그로 찾아뵙게 되었습니다.
Hub 기능 일시 중단 및 재오픈 안내
현재 블로그의 허브(Hub) 기능은 구글 애드센스 심사 진행을 위해 잠시 닫아둔 상태입니다. 심사 과정에서 사이트 구조의 일관성을 유지하고 불필요한 색인 혼선을 방지하기 위한 조치입니다.
애드센스 심사가 완료되는 대로 허브 시스템 전반에 대한 추가 점검과 코드 정비를 마친 뒤 다시 정상 오픈할 예정이니, 허브 기능을 기다려주시는 분들께서는 조금만 양해를 부탁드립니다.
이번 v1.0.0.11부터 v1.0.0.13까지의 여정은 새 기능을 무작정 늘리기보다, Cloudflare Free Tier라는 제약 조건 속에서 실질적인 성능과 안정성, 웹 표준 준수율을 끌어올리는 데 집중한 과정이었습니다.
결과적으로 3개 테마를 자유롭게 전환할 수 있는 디자인 유연성을 확보하면서도, 데이터베이스 읽기/쓰기 효율화와 모바일 CLS 0.000, 그리고 SEO 정규화까지 깔끔하게 마무리할 수 있었습니다.
SKBE의 전체 소스 코드는 공식 GitHub 저장소에 공개되어 있으니, SvelteKit 기반 서버리스 블로그 구축이나 성능 최적화에 관심 있으신 분들께 작은 참고가 되길 바랍니다.
댓글 0개
댓글을 작성하려면 로그인이 필요합니다.