Jerbix

Navigation

Navbar, footer, and socials

Last updated November 3, 2026


The navbar, the footer, and the docs sidebar share one menu data model: the same structure and the same filtering rules, so a change lands in all three.

FileResponsibility
src/config/navigation.tsMenu types, link targets, filtering rules
src/config/navbar-config.tsNavbar links and right-side buttons
src/config/footer-config.tsFooter groups
src/config/social-config.tsSocial platform mapping and safety filter

Edit src/config/navbar-config.ts and add an entry to the array in getNavbarLinks():

ts
// src/config/navbar-config.ts
{
  kind: 'link',
  id: 'pricing',
  title: 'Pricing',
  destination: { kind: 'internal', options: getPublicLinkOptions('pricing', locale) },
  visibility: { module: 'payment' },   // hidden when payment is off
}

Internal links go through getPublicLinkOptions(target, locale), which returns type-safe linkOptions(): a missing route or a missing dynamic parameter is rejected at tsc time.

targetResolves to
home/{locale}
docs/{locale}/docs
blog/{locale}/marketing/blog
changelog/{locale}/marketing/changelog
pricingThe homepage (getPublicLinkOptions('home', locale, 'pricing') is the variant that adds the anchor)
contact/{locale}/marketing/contact
roadmap/{locale}/marketing/roadmap
waitlist/{locale}/marketing/waitlist
login/{locale}/auth/login
terms / privacy/{locale}/marketing/legal/{slug}

The locale segment in these routes is optional ({-$locale}), so URLs in the default language carry no prefix.

Visibility rules

Every condition in visibility must hold for an entry to render:

FieldMeaning
moduleModule switch, e.g. { module: 'payment' }
featureFeature switch, e.g. { feature: 'enableApiKeys' }
pageStatic page switch, e.g. { page: 'waitlist' }
authenticatedWhether sign-in is required
rolesRole allowlist, e.g. { roles: ['admin'] }

Entries pointing at contact must carry visibility: { module: 'contact' } — that page is off by default and the link would 404. roadmap is an unconditional static page; waitlist has no module switch and is filtered by pages.waitlist.

The two shapes of a menu entry

kindNotes
linkInternal (destination: { kind: 'internal', options }) or external (destination: { kind: 'external', href, newTab })
groupRecursive children; the group disappears when every child is filtered out
separatorRemoved automatically at the start, end, or when duplicated

Links in the social field are validated as credential-free HTTPS URLs; the href of a custom menu entry is not validated, so do not put an address carrying a token there.

The footer lives in src/config/footer-config.ts and uses the same structure: getFooterLinks() returns two groups (product, resources), filtered by visibility just like the navbar.

Social links go in the social field of src/config/website.ts:

ts
social: {
  github: 'https://github.com/your-org/your-repo',
  x: 'https://x.com/your-handle',
}

src/config/social-config.ts maps each platform to an icon and label and renders only credential-free HTTPS URLs; anything else is dropped silently. A valid mail.supportEmail adds a mailto: entry.