본문 바로가기

사이트 검색 연동

RootTale 검색은 고객 사이트의 서버가 공개 CMS API를 호출하는 방식입니다. 검색창은 일반 GET 폼으로 만들되, ROOTTALE_API_KEY는 Server Component나 Route Handler 안에서만 사용합니다. 브라우저가 RootTale API를 직접 호출하지 않습니다.

권장 구조

방문자 브라우저
  GET /search?q=상담
        │
        ▼
고객 사이트 Server Component
  searchPosts({ type: "all", locale })
        │  Authorization: Bearer rtlk_cust_*
        ▼
RootTale 공개 검색 API
  tenant + site + locale + published 범위 검색

이 구조는 API 키를 숨기고, 검색 결과 페이지를 서버 렌더링하며, 고객사와 사이트 경계를 API에서 강제합니다. NEXT_PUBLIC_ROOTTALE_API_KEY처럼 공개 환경변수에 키를 넣으면 안 됩니다.

Next.js 구현

전체 예시는 examples/nextjs/app/search/page.tsx에 있습니다. 핵심 흐름은 다음과 같습니다.

import {
  fetchCollections,
  resolveSearchHitPath,
  searchPosts,
} from "@roottale/cms-client/server";

const [hits, collections] = await Promise.all([
  searchPosts({
    apiKey: process.env.ROOTTALE_API_KEY!,
    baseUrl: process.env.ROOTTALE_API_BASE,
    query,
    type: "all",
    locale,
    limit: 20,
    revalidate: 60,
  }),
  fetchCollections({
    apiKey: process.env.ROOTTALE_API_KEY!,
    baseUrl: process.env.ROOTTALE_API_BASE,
  }).catch(() => []),
]);

const results = hits.flatMap((hit) => {
  const href = resolveSearchHitPath(hit, collections, locale);
  return href ? [{ hit, href }] : [];
});

사이트 전체 검색은 type: "all"을 명시합니다. 이 값을 생략하면 하위 호환을 위해 글(post)만 검색합니다. 다국어 사이트는 현재 경로의 locale을 검색 API와 resolveSearchHitPath 양쪽에 같은 값으로 전달합니다.

결과 주소 계산

검색 결과의 slug만 보고 /blog/{slug}를 직접 만들지 않습니다. resolveSearchHitPath는 다음 규칙을 적용합니다.

콘텐츠공개 주소
고정 페이지/{slug}
기본 블로그 글/blog/{slug}
콘텐츠 유형 글/{collection.basePath}/{slug}
다국어 콘텐츠위 주소 앞에 /{locale} 추가
상세 화면이 없는 콘텐츠 유형null — 검색 목록에서 제외

따라서 fetchCollections가 실패해도 일반 글은 /blog/{slug}로 연결할 수 있지만, 공지·자료실 같은 별도 콘텐츠 유형의 주소 정확도를 위해 정상 응답을 권장합니다.

검색 범위와 정렬

  • API 키에 연결된 tenant와 site 밖의 콘텐츠는 검색하지 않습니다.
  • 요청한 locale의 published 콘텐츠만 반환합니다.
  • 제목 완전일치 → 제목 부분일치 → 요약 → 본문 순으로 우선합니다.
  • 같은 점수에서는 최근 발행 콘텐츠가 먼저 나옵니다.
  • 응답은 카드용 슬림 결과이며 본문 전체는 포함하지 않습니다.

현재 한 요청은 최대 50건입니다. 첫 버전에는 페이지네이션, 형태소 분석, 오타 교정, 동의어 확장이 없습니다. 실제 검색 로그에서 필요성이 확인되면 추가하는 범위입니다.

캐시와 새 글 반영

revalidate를 지정하면 Next.js 서버 캐시에 검색 응답이 저장됩니다. 예를 들어 revalidate: 60이면 발행·수정 후 검색 결과가 최대 약 60초 늦게 바뀔 수 있습니다. 항상 최신 결과가 필요하면 revalidate: 0을 사용하되 API 호출량 증가를 고려하세요.

플랫폼 배포 순서는 migration → API → SDK·고객 사이트입니다. 검색용 생성 열과 GIN 색인이 먼저 준비되어야 새 API가 안전하게 조회할 수 있습니다.

빈 결과와 장애 처리

  • 빈 검색어는 API를 호출하지 않고 입력 안내를 표시합니다.
  • 정상 응답이지만 결과가 없으면 검색어와 함께 0건 안내를 표시합니다.
  • API 장애는 빈 결과와 구분해 “잠시 후 다시 시도” 안내를 표시합니다.
  • searchPosts는 구 API의 404에 한해 빈 배열로 처리하고, 그 밖의 오류는 CmsApiError로 전달합니다.

검색 입력은 type="search", name="q", 연결된 <label>을 사용하고 결과 수는 aria-live="polite"로 알립니다. 검색 결과 페이지는 보통 중복·저가치 URL이므로 robots: { index: false, follow: true }를 권장합니다.

HTTP API

JavaScript 이외의 서버에서는 GET /v1/cms/public/search?q=...&type=all&locale=ko&limit=20을 사용합니다. 쿼리와 응답 필드는 HTTP API 레퍼런스를 참고하세요.