관리자 메뉴는 흔히 서버에서 내려옵니다. 역할마다 보이는 입구가 다르고, 페이지를 추가할 때 프런트엔드를 다시 배포할 필요도 없습니다. 할 일은 받은 대로 그리는 것이 아니라 메뉴, 라우트, 버튼 세 곳을 같은 규칙으로 묶는 것입니다.

메뉴 계약

메뉴는 그룹과 항목으로 내려옵니다. 항목은 경로, 라벨 또는 라벨 키, 아이콘, 소속 그룹, 정렬 순서, 숨김 표시, 권한 코드와 하위 항목을 담습니다. 그룹이 사이드바 순서를 정하고, 계층은 네 단계로 제한해 비정상적인 중첩이 사이드바를 무너뜨리지 않게 합니다.

외부 링크 항목은 주소와 열기 방식을 스스로 선언합니다. 새 창, 같은 창, 임베드 중 하나입니다. 임베드는 허용한 사이트에서만 동작하며, X-Frame-Options: DENY를 보내는 사이트는 빈 화면이 됩니다.

관대한 처리는 프런트엔드의 몫입니다. 비정상 경로, 알 수 없는 아이콘, 너무 깊은 하위 항목은 정리하고 읽을 수 있는 경고 목록을 남깁니다. 실제 API를 연결할 때는 사용자 신고를 기다리지 말고 이 목록을 먼저 봅니다.

라우트는 등록된 페이지만 안다

동적 라우트의 컴포넌트는 로컬 등록표에서 가져옵니다. API는 등록된 페이지를 참조할 수 있을 뿐 새 코드를 들여올 수 없습니다. 로그인하지 않았거나 권한이 없는 주소는 빈 화면이 아니라 로그인 페이지나 403으로 떨어집니다.

세 곳에 같은 규칙

  • 메뉴: 권한 코드로 항목을 걸러 권한 없는 입구는 그리지 않습니다.
  • 라우트: 주소를 직접 입력해도 같은 가드에 걸려 403이 됩니다.
  • 버튼: <Can permission="customer:create">가 생성과 내보내기 같은 동작을 제어하고, 권한이 없으면 조용히 실패하지 않고 비활성 상태를 보여 줍니다.

권한 코드는 *(전체), module:*(모듈 전체), 정확 일치 세 가지로 쓸 수 있어 역할 차이를 한 목록으로 표현합니다. 문서 사이트에 계약 전문이 있고, 온라인 데모에서는 두 계정을 비교할 수 있습니다. 같은 주소에서도 관리자는 감사 로그를 보고 테스트 계정은 403을 받습니다.