사이트 검색 연동
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 레퍼런스를 참고하세요.