Navigation Configuration
Configure and customize navigation menus, headers, footers, and sidebars in your SaaS application.
Customize your app's navigation by editing TypeScript configuration files. Marketing pages use component-based navigation; the dashboard uses a declarative config that adapts to account modes.
Navigation configuration controls the links and menu structure users see throughout the application. MakerKit separates marketing navigation (header/footer) from app navigation (sidebar/mobile menu) for independent customization.
This page is part of the Configuration documentation.
Navigation Locations
| Location | File | Used For |
|---|---|---|
| Header | app/[locale]/(public)/_components/site-navigation.tsx | Marketing site top nav |
| Footer | app/[locale]/(public)/_components/site-footer.tsx | Marketing site footer |
| Sidebar | config/app-navigation.config.tsx | Dashboard sidebar |
| Admin | config/admin-navigation.config.tsx | Admin panel sidebar |
Add routes to navigation when: you have new pages users need to discover. Most new features need a sidebar entry.
Avoid cluttering navigation: group related items, use sub-menus for secondary pages, and consider whether a page really needs a nav link or just internal links.
If unsure: add routes to the sidebar config - it's the primary navigation for authenticated users.
Header Navigation (Marketing)
Edit apps/web/app/[locale]/(public)/_components/site-navigation.tsx:
const links = { Blog: { label: 'marketing.blog', // i18n key path: '/blog', }, Docs: { label: 'marketing.help', path: '/help', }, Pricing: { label: 'Pricing', // Or use plain text path: '/pricing', },};The label can be an i18n translation key (like 'marketing.blog') or plain text (like 'Pricing'). Links render in the order defined.
Footer Navigation
Edit apps/web/app/[locale]/(public)/_components/site-footer.tsx:
<Footer logo={<AppLogo className="w-[85px] md:w-[95px]" />} description={<Trans i18nKey="marketing.footerDescription" />} copyright={ <Trans i18nKey="marketing.copyright" values={{ product: appConfig.name, year: new Date().getFullYear(), }} /> } sections={[ { heading: <Trans i18nKey="marketing.about" />, links: [ { href: '/blog', label: <Trans i18nKey="marketing.blog" /> }, { href: '/contact', label: <Trans i18nKey="marketing.contact" /> }, ], }, { heading: <Trans i18nKey="marketing.legal" />, links: [ { href: '/terms-of-service', label: <Trans i18nKey="marketing.termsOfService" /> }, { href: '/privacy-policy', label: <Trans i18nKey="marketing.privacyPolicy" /> }, ], }, ]}/>Footer sections appear as columns. Keep legal links (terms, privacy, cookies) even if you customize everything else.
Dashboard Sidebar
The sidebar is configured declaratively in apps/web/config/app-navigation.config.tsx. The schema (validated with Zod) lives at packages/ui/src/makerkit/navigation-config.schema.ts.
Basic Structure
const routes: z.output<typeof NavigationConfigSchema>['routes'] = [ { label: 'common.routes.application', // Group heading children: [ { label: 'common.routes.dashboard', path: '/dashboard', Icon: <Home className={iconClasses} />, }, ], },];Routes are organized into groups (with label and children). Each child has:
label: i18n key or plain textpath: URL pathIcon: Lucide icon component
Adding Routes
Add a Chat page and Settings group:
const routes: z.output<typeof NavigationConfigSchema>['routes'] = [ { label: 'common.routes.application', children: [ { label: 'common.routes.dashboard', path: '/dashboard', Icon: <Home className={iconClasses} />, }, { label: 'common.routes.chat', path: '/dashboard/chat', Icon: <MessageSquare className={iconClasses} />, }, ], }, { label: 'common.routes.settings', children: [ { label: 'common.routes.profile', path: '/settings/profile', Icon: <User className={iconClasses} />, }, ], },];Don't forget to add translation keys to public/locales/en/common.json:
{ "routes": { "chat": "Chat", "profile": "Profile" }}Context-Aware Routes
Show different routes based on whether the user is viewing their personal account or an organization:
{ label: 'common.routes.team', path: '/dashboard/team', Icon: <Users className={iconClasses} />, context: 'organization', // Only in organization context},{ label: 'common.routes.personalSettings', path: '/dashboard/personal', Icon: <User className={iconClasses} />, context: 'personal', // Only in personal context},Context values:
'all'(default): Always visible'personal': Only when viewing personal account'organization': Only when viewing an organization
Context works with your Account Mode:
- Personal-only mode:
context: 'organization'routes are always hidden - Organizations-only mode:
context: 'personal'routes are always hidden - Hybrid mode: Routes show/hide based on current context
Nested Sub-menus
For complex features, add nested children:
{ label: 'common.routes.projects', path: '/dashboard/projects', Icon: <Folder className={iconClasses} />, children: [ { label: 'common.routes.allProjects', path: '/dashboard/projects', }, { label: 'common.routes.archived', path: '/dashboard/projects/archived', }, ],},Sub-children can also have the context property for granular control.
Common Pitfalls
- Missing translations: If you use i18n keys, add them to your locale files. Missing keys show the raw key string.
- Wrong path format: Paths should start with
/and match your file-based routes./dashboard/chatneedsapp/home/[account]/chat/page.tsxto exist. - Forgetting context in hybrid mode: Routes without
contextshow everywhere. Team-only features should usecontext: 'organization'. - Icon import errors: Import icons from
lucide-react. TheiconClassesvariable provides consistent sizing. - Stale navigation after account switch: Navigation re-renders on context change, but ensure your paths don't hardcode account slugs.
Route Properties Reference
| Property | Type | Required | Description |
|---|---|---|---|
label | string | Yes | i18n key or display text |
path | string | Yes | URL path |
Icon | ReactNode | No | Lucide icon component |
context | 'all' | 'personal' | 'organization' | No | When to show route |
children | RouteChild[] | No | Nested sub-menu items |
end | boolean | No | Exact path matching for active state |
Frequently Asked Questions
How do I add external links to the navigation?
Can I hide navigation groups based on permissions?
How do I highlight the active route?
Where do I add admin navigation?
Next: Authentication →