블로그 하루 개발 기록: 리전 이동과 CDN 캐시, 새 페이지 3개, 메뉴 정리

· 약 7분 읽기 · 조회 4 #개발기록#Claude Code#SvelteKit#Vercel#SEO#GEO

오늘은 하루 종일 Claude Code와 블로그를 손봤다. 커밋은 9개다. 크게 보면 세 가지다.

  1. 검색에 잘 걸리게: 네이버 서치어드바이저 등록, 파비콘, GEO 적용
  2. 빠르게: 서버 리전 이동, CDN 캐시
  3. 보여줄 게 많게: 영상, 기술 보고서, 만든 사이트 페이지를 새로 만들고 메뉴 정리

1. 네이버 서치어드바이저 등록과 파비콘

네이버에 사이트를 등록하려면 인증 메타 태그를 홈 <head>에 넣어야 한다. app.html에 구글 인증 태그 옆으로 넣었다. 이 블로그 운영자 이름도 "블로그 주인"에서 "용데브"로 바꾸고, 사이트 설명도 실제로 올리는 글(매일 AI 뉴스, 주간 레딧 베스트, Claude Code·하네스 개발 기록)이 드러나게 고쳤다.

파비콘은 원래 이모지를 data URI로 넣어 둔 상태였다. 검색 결과에 아이콘이 제대로 안 나와서 초록 바탕에 만년필 펜촉 아이콘을 만들어 favicon.ico(16/32/48)와 apple-touch-icon.png(180)로 넣었다.

2. 페이지가 1초씩 걸리던 이유: 리전

글 하나 여는 데 1초 가까이 걸렸다. 원인은 단순했다. DB(Turso)는 도쿄에 있는데 Vercel 함수는 기본값인 **미국 동부(iad1)**에서 돌고 있었다. 쿼리 한 번마다 태평양을 왕복한 셈이다.

// svelte.config.js
adapter: adapter({ runtime: 'nodejs22.x', regions: ['hnd1'] }) // Turso DB(도쿄)와 같은 리전

한 줄 바꾸고 다시 쟀다.

페이지 바꾸기 전 바꾼 후
홈 약 900~1060ms 약 157~459ms
글 페이지 약 1000ms 약 122~231ms

DB와 서버가 멀리 떨어져 있으면 코드 최적화보다 이게 먼저다.

3. CDN 캐시와 "쓰면 지운다"

리전만으로도 빨라졌지만 블로그 글은 대부분 읽기만 한다. 매번 서버에서 렌더링할 이유가 없다. 그래서 공개 페이지를 Vercel CDN에 1시간 올려 두기로 했다.

문제는 글을 고치거나 댓글이 달렸을 때다. 캐시에 옛날 페이지가 남아 있으면 안 된다. 그래서 규칙을 이렇게 정했다.

  • GET 요청: 관리자가 아니고, 관리자·API 경로가 아니고, 정상 응답(200)이고, 쿠키를 굽지 않는 응답이면 CDN 캐시 헤더와 pages 태그를 붙인다.
  • 그 외 요청(POST 등): 글·댓글·방명록 쓰기가 성공하면 pages 태그 캐시를 통째로 지운다.
// src/hooks.server.ts (요약)
if (method === 'GET' || method === 'HEAD') {
	if (cacheable) {
		response.headers.set('Vercel-CDN-Cache-Control', 'max-age=3600, stale-while-revalidate=86400');
		response.headers.set('Vercel-Cache-Tag', 'pages');
	}
} else if (response.status < 400) {
	await dangerouslyDeleteByTag('pages');
}

함정이 하나 있었다. 조회수다. 글을 열 때 서버에서 조회수를 올리고 있었는데, 캐시가 응답하면 서버 코드가 안 돌아서 조회수가 멈춘다. 그래서 조회수 증가를 페이지 로딩에서 떼어 내고, 브라우저가 navigator.sendBeacon으로 /api/views/[slug]에 따로 알리게 바꿨다. 이 API는 캐시 삭제 대상에서 뺐다. 조회수가 오를 때마다 캐시를 지우면 캐시를 쓰는 의미가 없으니까.

결과는 캐시 적중 시 약 10ms. 글을 쓰면 다음 요청에서 바로 새 내용이 나오는 것도 확인했다.

4. 영상 페이지 (/videos)

유튜브 채널 아키의 공백에 올린 영상 14개를 모은 페이지다. 유튜브 플레이어를 14개 한꺼번에 띄우면 페이지가 무거워져서, 처음엔 썸네일만 보여 주고 누른 영상만 youtube-nocookie 플레이어로 바꿔 끼운다. 영상마다 VideoObject 구조화 데이터도 넣었다.

5. GEO: AI 검색에 인용되게

요즘은 구글만큼 ChatGPT·Perplexity·Claude 같은 AI 검색에서 어떻게 보이는지도 중요하다. 이걸 GEO(Generative Engine Optimization)라고 부른다. 조사해 보니 결국 "AI가 이 사이트를 읽을 수 있고, 누가 썼는지 확인할 수 있고, 새 글을 빨리 알 수 있는가"였다. 그래서 세 가지를 했다.

  • 작성자 연결: 구조화 데이터의 작성자(Person)에 유튜브 채널과 크몽 프로필을 sameAs로 연결했다. "용데브"가 같은 사람이라는 걸 기계가 알 수 있게.
  • IndexNow: 글을 발행·수정·삭제하면 바뀐 주소를 IndexNow로 바로 알린다. 빙과 네이버가 이걸 받는다. 응답을 기다리지 않도록 waitUntil로 백그라운드에서 보낸다.
  • llms.txt 보강: AI가 읽는 사이트 안내 파일에 소개·전자책·영상 페이지를 추가했다.

그리고 GPTBot, OAI-SearchBot, ClaudeBot, PerplexityBot, bingbot, 네이버 Yeti, Googlebot 이름으로 접속해 봤다. 전부 200과 함께 본문 전체를 받는다.

6. 기술 보고서 페이지 (/reports)

그동안 써 둔 아키텍처 설계서와 기술 리서치 보고서 9개를 기술 보고서 페이지에 올렸다. 각 보고서는 웹으로 바로 보기, PDF, Word 세 가지로 열 수 있다.

보고서는 다른 폴더에서 계속 늘어나기 때문에 손으로 옮기지 않고 스크립트(scripts/sync-reports.py)를 만들었다. HTML·PDF·DOCX와 본문 이미지를 블로그로 복사하고, 첫 이미지로 썸네일을 만들고, 제목과 요약을 뽑아 목록 데이터(JSON)를 만든다. 새 보고서가 생기면 스크립트 한 번 돌리고 배포하면 된다.

7. 만든 사이트 페이지 (/projects)

마지막으로 직접 만든 웹 서비스 7개를 소개하는 만든 사이트 페이지를 만들었다.

  • GovSales Intelligence: 공공 IT 사업 영업 워크스페이스
  • Geek 조간: GeekNews·Hacker News 아침 요약
  • 김용연 포트폴리오: 개발자 소개 랜딩 페이지
  • 프리랜서 실수령액 계산기: 계약 형태 5가지 실수령액 비교
  • Decimen Optical Transfer: 움직이는 QR 코드로 화면에서 카메라로 파일 전송
  • 시드온: 실제 시세로 하는 가상 주식 트레이닝 게임
  • 하이월드: 40·50대를 위한 복고풍 일촌 소셜 네트워크

카드 썸네일은 각 사이트 첫 화면을 캡처해서 640×360으로 줄여 썼다.

8. 메뉴가 10개가 됐다

페이지를 계속 늘리다 보니 PC 헤더 메뉴가 10개가 됐다. 한 줄에 겨우 들어가긴 했지만 지저분했다. 그래서 세 그룹으로 묶었다.

  • 글: 글 목록, 공지사항, 검색
  • 만든 것: 만든 사이트, 전자책, 영상, 보고서
  • 소개: 블로그 소개, 방명록, 로그인

PC에서는 그룹을 누르면 아래로 펼쳐지고, 모바일에서는 햄버거 메뉴 안에서 같은 그룹이 아코디언으로 열린다. 지금 보는 페이지가 속한 그룹은 진하게 표시된다. Esc, 바깥 클릭, 페이지 이동 때 닫히게 했다.

돌아보며

  • 제일 효과가 컸던 건 한 줄짜리 리전 변경이었다. 느리면 코드보다 먼저 "서버와 DB가 어디 있나"를 본다.
  • 캐시를 붙이면 캐시 때문에 안 돌게 되는 서버 코드(이번엔 조회수)를 꼭 찾아야 한다.
  • 페이지를 늘리면 메뉴도 같이 정리해야 한다. 기능을 더하는 것보다 묶는 게 더 오래 걸렸다.

남은 일은 커스텀 도메인 연결이다. *.vercel.app 주소는 검색 신뢰도에서 손해를 본다.

댓글 0개

아직 댓글이 없습니다. 첫 댓글을 남겨보세요.

댓글 쓰기

← 목록으로 돌아가기