Menu Guide
Why menus matter in Cabloy SSR flows
In Cabloy, SSR menu retrieval can be treated as a reusable backend capability instead of a one-off frontend-only concern.
That matters because shared menu retrieval makes it easier to reuse navigation logic across modules and editions.
Core SSR menu model
The a-ssr module provides a general SSR menu system.
A project can ignore the system menu model and implement its own retrieval logic, but the built-in model is useful because it improves reuse, consistency, and scalability.
bean.ssr
Vona exposes a global bean bean.ssr for SSR-facing menu retrieval.
A representative usage pattern is:
const res = await this.bean.ssr.retrieveMenus(publicPath);This allows a module-level menu service to:
- ask the shared SSR menu system for the effective menu set
- fall back to default local menus when needed
The current home-base implementation in this repo follows exactly that pattern in its menu service, which keeps this guide grounded in the real out-of-the-box SSR path.
Fallback strategy
A practical menu service can:
- try shared SSR menu retrieval first
- return a module-defined default menu if no shared menu is available
That keeps menu integration flexible without giving up framework-level reuse.
Menu API shape
A backend controller can expose the menu retrieval path directly:
@Web.get(':publicPath?')
@Api.body(v.object(DtoMenus))
@Passport.public()
async retrieveMenus(@Arg.param('publicPath', v.optional()) publicPath?: string) {
return await this.scope.service.menu.retrieveMenus(publicPath);
}2
3
4
5
6
This makes menu retrieval part of the broader backend contract surface.
In the current repo implementation, the out-of-the-box menu controller is public and delegates directly to this.scope.service.menu.retrieveMenus(publicPath).
Static menu visibility
@SsrMenu(...) items can declare static role visibility without changing the public menu DTO:
@SsrMenu({
item: {
title: $locale('Operations'),
link: 'presetResource',
roles: ['systemAdmin'],
},
site: 'basic-siteadmin:admin',
})2
3
4
5
6
7
8
- Omit
roles, or useroles: [], to make an item visible to anonymous and authenticated callers. - A nonempty
rolesarray is visible when the current Passport has at least one matching role name. rolesis server-only declaration metadata. It is filtered out before the API response and is not part ofIMenuItem, OpenAPI, or generated frontend clients.- This controls navigation disclosure only. It never grants access to a page, controller action, API, or resource; those boundaries retain their own route and Passport/permission guards.
SSR Site menu definitions are cached structurally by Site and locale. The framework keeps static role policy in that prepared cache, then creates a filtered response for each request without mutating the cached definition.
Frontend query lifecycle
Passport filtering does not add user or role identity to the frontend menu query key. The menu resource remains keyed by stable inputs: Site/public path and locale.
On login, ModelPassport.afterLogin() stores the Passport/JWT before returning to the destination layout. The layout's ordinary $useStateData(...) lifecycle refreshes stale menu data using the newly authenticated request context. On logout, the Passport model navigates to login and then clears query data.
A dynamic role or menu-policy change while the user remains signed in is different: the mutation owner must explicitly refresh authoritative Passport state when needed and invalidate or refetch the affected menu query. This is UI freshness behavior only; route, controller, API, and resource authorization remain enforced independently by their existing guards.
Relationship to frontend integration
Menu retrieval is especially relevant in SSR-sensitive frontend flows.
The backend menu contract should be read together with frontend routing, SSR, and page-loading behavior, especially when different editions expose different module or menu structures.
A practical boundary is:
- backend decides how menus are retrieved, merged, and defaulted
- frontend decides how those menu DTOs are rendered into route or navigation state
That split helps avoid re-implementing menu policy independently on both sides.
Implementation checks for SSR menu changes
When editing SSR menu behavior, ask:
- should the logic use
bean.ssr.retrieveMenus(...)instead of inventing a parallel retrieval path? - is there a default fallback menu that should remain available?
- does the menu contract belong in backend API design, frontend route design, or both?
- does the active edition affect the menu structure or public path assumptions?
That helps AI keep menu behavior aligned with Cabloy’s shared SSR architecture.