🛠️ 블로그 · 2026-05-09

빌드 노트 — 검색창·카드 우선순위·AdSense 심사 대응 (2026-05-09)

하루 작업 기록. 홈 검색창 추가, 카드 우선순위 데이터화, AdSense 심사 대응 코드 변경. 결정 이유와 코드 스니펫 포함.

2026-05-09 토요일 작업 노트. 오늘 처리한 변경 3가지를 결정 맥락과 함께 기록해 둔다. 작업 시간 약 4시간, 모두 bal-hub 단일 레포 안에서 처리했고 deploy 는 다음 호흡에 별도로 한다.

작업 1 — 홈 검색창

왜 추가했나

/ 진입 사용자가 119개 도구 중 본인이 찾는 것을 빠르게 찾을 방법이 없었다. 기존에는 허브별 그리드만 있었고, 사용자가 도구 이름을 알면서도 "어느 허브에 있더라" 를 한 번 더 클릭해야 했다. GA4 의 클릭 경로 분석에서, 홈 진입 후 평균 2.4 클릭 후 도구 상세에 도달하는 패턴이 보였다. 이걸 1 클릭으로 줄이는 게 목표였다.

결정 — 클라이언트 사이드 즉시 매칭

서버 검색 인덱스는 과한 선택이었다. 119개의 메타데이터(name, slug, description, hubId, tags) 합쳐 100KB 미만이라, 그냥 메모리에서 Array.filter + 토큰 매칭으로 충분하다. 빌드 시 모든 데이터가 정적으로 들어가 있어서 즉시 응답.

```tsx

const matched = useMemo(() => {

if (!q.trim()) return [];

const tokens = q.toLowerCase().split(/\s+/).filter(Boolean);

return TOOLS.filter((t) => {

const haystack = [

t.name[locale], t.slug, t.description?.[locale] ?? '', t.hubId,

...(t.tags?.[locale] ?? []),

].join(' ').toLowerCase();

return tokens.every((tok) => haystack.includes(tok));

}).slice(0, 12);

}, [q, locale]);

```

3가지 정책 결정.

  1. AND 매칭 (모든 토큰 포함). OR 로 했더니 결과가 너무 산만했다.
  2. 상위 12개 컷. 그 이상은 사용자가 어차피 안 본다.
  3. 부분 일치 허용. includesstartsWith 보다 한국어 검색에서 훨씬 너그럽다.

키보드 동선

ARIA combobox 패턴까지 가지 않고 단순 input + 결과 리스트로 처리. 단, ↑/↓ 키 이동 + Enter 선택은 추가했다. 모바일에서는 검색창이 sticky 가 아니라 그냥 페이지 상단 배치로 결정. sticky 검색은 첫 방문자에게 의외로 거슬린다는 게 다른 도구에서 학습한 결과다.

작업 2 — 카드 우선순위 데이터화

문제

홈에 노출되는 도구 카드의 순서가 그동안 tools.ts 의 배열 순으로 노출됐다. 즉, 운영자가 코드 순서를 의도적으로 정리해두지 않으면 노출 우선순위가 어떻게 굳어 있는지 추적이 어려웠다. 5월 7일에 있었던 yebang(예방접종) 트래픽 급등(138 UV) 같은 시그널을 운영자가 즉시 우선순위에 반영하기 어려웠다.

결정 — 별도 JSON 으로 분리

src/data/card-priority.json 파일을 신설해 도구 slug → 우선순위 점수(0~100) 의 매핑을 둔다. 점수의 정의는 다음과 같이 단순하게 정했다.

  • 90~100: 직전 30일간 일평균 UV 상위 5
  • 70~89: 직전 30일간 신규 출시 또는 viral spike 경험
  • 50~69: 안정 운영, 검색 인입 일정
  • 30~49: 평균 이하지만 카테고리 정합성 유지에 필요
  • 0~29: 노출 비우선 (백 리스트)

HomePageHubPage 의 카드 정렬 로직은 다음 한 줄로 압축했다.

```ts

const sortedTools = [...tools].sort(

(a, b) => (priority[b.slug] ?? 50) - (priority[a.slug] ?? 50)

);

```

기본값을 50 으로 둔 이유는, 신규 도구가 들어왔을 때 명시적으로 점수를 안 적어도 중간 위치에 자연스럽게 끼도록 하기 위함이다. 점수를 의도적으로 0 으로 내리지 않으면 노출에서 사라지지 않는다.

운영 절차

매주 일요일 GA4 + Search Console 데이터로 카드 우선순위 JSON 을 1번 갱신하기로 했다. 사람이 직접 갱신하는 게 핵심이다. 자동화하면 하루치 노이즈에 우선순위가 출렁거린다. 사람의 판단(이 도구는 일시적 spike 인지, 진짜 자리 잡았는지) 을 한 번 끼워야 안정적이다.

작업 3 — AdSense 심사 대응

배경

지난 주 AdSense 심사 결과에서 일부 사이트의 "콘텐츠 부족" 사유가 있었다. bal-hub 자체는 이미 콘텐츠가 충분하지만, AI/프로그래매틱 의심을 줄이기 위한 인간 시그널 강화가 필요하다는 판단을 했다. 24편의 블로그·가이드가 모두 비슷한 "한 줄 요약 → H2 비교 → 케이스 → 정리 → 관련 도구" 구조를 따르고 있어서, 패턴이 너무 균일했다.

결정 — 톤·구조가 다른 블로그 5편 추가 + /log 운영 노트 페이지 신설

5편을 의도적으로 5가지 다른 형식으로 작성했다.

  1. 1인칭 1년 회고 (이 글이 아니라 별도 글)
  2. Q&A 12개
  3. 데이터 분석 (5년 검색 트렌드)
  4. 셀프 인터뷰
  5. 빌드 노트 (이 글)

각 글은 같은 패턴을 의도적으로 깬다. 헤딩 위주 vs 단락 위주, Q→A 반복 vs 표 분석, 코드 스니펫 포함 vs 미포함. 균일한 4,000자짜리 H2-H3 트리 5개를 더 추가하는 것보다, 톤이 다른 5개가 사람이 만든 사이트라는 시그널을 더 강하게 준다.

추가로 /log 페이지를 신설해 50~300자 짜리 짧은 운영 노트를 시간순 역으로 쌓는다. 여기에 들어가는 글은 SEO 가 목표가 아니라 "이 사이트는 사람이 매일 운영한다" 는 시그널을 주는 게 목표다.

코드 변경 요약

  • src/data/posts.ts — 블로그 5개 추가
  • src/pages/LogPage.tsx — 신규 페이지
  • src/data/log-entries.ts — 운영 노트 데이터
  • src/App.tsx/log, /en/log 라우트 추가
  • scripts/routes.mjs — prerender 라우트 목록에 /log 추가

prerender 가 자동으로 /log 도 정적으로 굽도록 routes.mjsstaticPaths 배열에 'log' 를 추가했다. SSR 결과는 다음 deploy 에서 dist/log/index.html 로 떨어진다.

작업 후 점검

  • npx tsc --noEmit 통과
  • 로컬 npm run dev 에서 검색창·카드 정렬·신규 라우트 모두 정상
  • prerender dry-run 에서 라우트 카운트 +6 (블로그 5 + log 1, 한·영 합치면 +6)

다음 호흡

deploy 는 따로. 코드 변경만 정리하고 오늘은 닫는다. 다음에는 (a) AdSense 슬롯 누락 사이트 일괄 점검 (b) 상위 5개 사이트의 검색창 적용 여부 검토 (c) /log 운영 자동화 (Threads 게시 → log 자동 인입) 까지 가는 것이 계획.

---

이 빌드 노트는 운영자 본인을 위해 쓰는 기록이지만, 같은 처지의 1인 개발자에게도 도움이 될까 싶어 공개로 둔다. 일자별 빌드 로그를 /log 에서도 더 짧게 볼 수 있다.

관련 도구