Build-Time Data와 Instant Search로 내 정적 블로그 재구축
Source: Dev.to

정적 사이트는 빠르고, 간단하며, 신뢰할 수 있어야 합니다. 시간이 지나면서 내 개인 블로그는 동적 앱처럼 동작하기 시작했어요—런타임 API 호출, 어디에나 있는 페이지네이션 로직, 그리고 여러 플랫폼에 흩어져 있는 조회수 집계 등.
지난 주에 ravgeet.in(Nuxt.js)의 블로그 섹션을 완전히 다시 만들면서 이 문제를 제대로 해결했습니다. 최종 결과물은 여전히 정적 사이트이지만, 이제는 생동감을 느낍니다: 집계된 조회수, 즉시 검색 및 정렬 기능, 그리고 외부 API에 대한 런타임 의존성이 전혀 없습니다.
이 글에서는 그 재구축 과정에서의 사고 흐름, 아키텍처, 그리고 트레이드‑오프를 자세히 살펴봅니다.
내 기존 설정의 문제점
- 블로그 콘텐츠는 Hashnode에 존재했으며 (정식 출처)
- 일부 게시물은 Dev.to에도 교차 게시되었습니다
- 페이지는 Hashnode의 GraphQL API를 사용하여 런타임에 블로그 데이터를 가져왔습니다
- 페이지네이션 로직(
hasNextPage, 커서)은 UI 내부에 존재했습니다
단점
- 실시간 API에 의존하는 정적 사이트는 어색하게 느껴졌습니다
- 로컬 개발 및 빌드가 느리고 불안정했습니다
- 검색이나 정렬 같은 기능을 추가하려면 더 많은 API가 필요했습니다
블로그는 정적으로 유지하되, 더 똑똑해지길 원했습니다.
빌드 시점 데이터 계약으로
외부 데이터 가져오기를 모두 빌드 시점으로 옮기고, 결과를 불변(static) 데이터로 취급합니다.
런타임에 블로그를 가져오는 대신, 다음과 같은 빌드 단계를 도입했습니다:
- Hashnode에서 블로그를 가져옵니다
- Dev.to에서 글을 가져옵니다
- 플랫폼 간 동일한 글을 매칭합니다
- 조회수를 집계합니다
- 모든 데이터를 하나의 JSON 파일에 기록합니다
런타임에서는 사이트가 해당 JSON만 읽습니다.
Hashnode + Dev.to
↓
Build‑time fetch & normalize
↓
static/blogs.json
↓
Nuxt UI (search, sort, views)
블로그 데이터 가져오기 및 집계
Hashnode: 정규 콘텐츠
Hashnode는 다음에 대한 진실된 출처입니다:
- 제목, 슬러그, 내용, 태그
- 게시 날짜
- 표지 이미지
- 기본 조회 수
모든 게시물은 Hashnode의 GraphQL API를 사용해 가져오며, 페이지네이션은 Node.js 스크립트 내부에서 처리됩니다.
Dev.to: 배포 및 추가 도달
Dev.to는 추가 독자를 제공하므로 해당 조회수를 무시하는 것이 잘못된 느낌이었습니다. 개인 액세스 토큰을 사용한 Dev.to API를 이용해 모든 글을 가져와 다음을 추출합니다:
slugcanonical_urlpage_views_count
플랫폼 간 기사 매칭
기사 매칭은 단계적 전략을 사용합니다:
- 슬러그 매치
- 정규 URL 매치
- 제목 매치
매칭이 완료되면 최종 조회 수는 다음과 같이 됩니다:
combinedViews = hashnodeViews + devtoViews;
각 블로그에 대한 출력에는 다음이 포함됩니다:
- 합산 조회수
- 플랫폼별 조회수 (디버깅용)
- Dev.to URL (매칭된 경우)
정적 데이터 계약 작성
모든 처리된 데이터가 static/blogs.json에 기록됩니다. 이 파일은:
- 빌드 시 생성됨
- Git에서 무시됨
- 앱에서 읽기 전용으로 취급됨
또한 마지막 업데이트 시간 및 전체 블로그 수와 같은 메타데이터를 포함하여, 사실상 전체 블로그 API를 대체합니다.
런타임 API를 정적 서비스로 교체하기
이전에는 services/blogs.js가 실시간 GraphQL 호출을 수행했습니다. 리팩터링 후:
- 서비스가
blogs.json을 동적으로 import합니다 find,findOne,search가 로컬에서 동작합니다- Axios 없음, 페이지네이션 상태 없음, 네트워크 실패 없음
UI 관점에서는 아무 변화가 없었지만, 내부적으로는 모든 것이 예측 가능해졌습니다.
즉시 검색 및 정렬
With all blog data local, search becomes trivial. I added:
- Client‑side text search (title, brief, tags)
Sorting options
- Published date (recent / oldest)
- View count (most / least)
Because the dataset is small and static:
- Search results are instant
- No debouncing hacks
- No loading states
- Sorting is deterministic
This dramatically improves discoverability without introducing a search service.
트레이드오프와 배운 교훈
- 빌드 시간이 약간 증가합니다
- JSON 파일이 시간이 지남에 따라 커집니다
- 실시간 분석에는 적합하지 않습니다
하지만 개인 블로그에선 이러한 트레이드오프가 충분히 가치가 있습니다. 핵심 요점
- 정적이라고 해서 생명력이 없는 것은 아니다
- 빌드 시점 데이터 파이프라인은 과소평가되고 있다
- 하나의 깔끔한 데이터 계약이 UI, UX, 그리고 성능을 단순화한다
궁금하시다면 전체 구현은 ravgeet.in 리포지토리에 있습니다.