메뉴 (네비게이션) 연동
어드민(mysite.roottale.com)의 디자인 > 메뉴에서 저장한 네비게이션 트리를 사이트의 헤더·푸터에 렌더합니다. 고객이 직접 메뉴 항목(이름·주소·순서·하위 항목)을 바꿀 수 있어 코드 수정 없이 네비가 갱신됩니다.
- 메뉴는 위치 핸들(slug) 로 구분합니다 — 관례:
primary(헤더),footer(푸터). - 항목은 깊이 2 트리 (상위 + 드롭다운 1단).
url은 상대 경로(/about) 또는 절대http(s)URL — 서버가 위험 스킴을 제거한 안전한 값만 내려줍니다.- 메뉴 저장 시 발행 웹훅(
post.updated, paths:["/"])이 발송되므로 웹훅 수신 라우트(revalidation-webhooks.md)를 설정했다면 몇 초 내 반영됩니다.
서버 컴포넌트에서 메뉴 가져오기
// components/site-nav.tsx (Server Component)
import Link from "next/link";
import { fetchMenu } from "@roottale/cms-client/server";
export async function SiteNav() {
const menu = await fetchMenu({
apiKey: process.env.ROOTTALE_API_KEY!,
slug: "primary",
});
// 메뉴 미설정(null)이면 자체 fallback 네비를 렌더하세요.
if (!menu) {
return (
<nav>
<Link href="/blog">블로그</Link>
</nav>
);
}
return (
<nav>
{menu.items.map((item) => (
<div key={item.id}>
<Link
href={item.url}
{...(item.newTab
? { target: "_blank", rel: "noopener" }
: {})}
>
{item.label}
</Link>
{item.children?.length ? (
<div>
{item.children.map((child) => (
<Link key={child.id} href={child.url}>
{child.label}
</Link>
))}
</div>
) : null}
</div>
))}
</nav>
);
}
fetchMenu 는 미존재/구 서버의 404 를 null 로 돌려주므로(fail-soft) 항상
fallback 분기를 두세요. 전체 메뉴 목록이 필요하면 fetchMenus({ apiKey }).
타입
interface RootTaleMenu {
id: string;
name: string; // "헤더 메뉴"
slug: string; // "primary" | "footer" | ...
items: RootTaleMenuItem[];
updatedAt: string | null;
}
interface RootTaleMenuItem {
id: string;
label: string;
url: string; // "/about" 또는 "https://..."
newTab?: boolean; // target="_blank" rel="noopener" 로 렌더
children?: RootTaleMenuItem[];
}
캐싱
메뉴 응답은 5분 캐시(+SWR)로 내려갑니다. 페이지가 ISR 이라면 발행 웹훅이
/ 를 revalidate 할 때 함께 갱신되므로 별도 처리 없이 near-real-time 입니다.
raw HTTP
JS 외 스택은 GET /v1/cms/public/menus/{slug} 를 직접 호출하세요 —
api-reference.md 참고.