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.

LocationFileUsed For
Headerapp/[locale]/(public)/_components/site-navigation.tsxMarketing site top nav
Footerapp/[locale]/(public)/_components/site-footer.tsxMarketing site footer
Sidebarapp/[locale]/(internal)/_config/navigation.config.tsxDashboard sidebar
Adminpackages/admin/src/admin-sidebar.tsxAdmin 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.

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/app/[locale]/(internal)/_config/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 text
  • path: URL path
  • Icon: 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/chat needs app/home/[account]/chat/page.tsx to exist.
  • Forgetting context in hybrid mode: Routes without context show everywhere. Team-only features should use context: 'organization'.
  • Icon import errors: Import icons from lucide-react. The iconClasses variable 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

PropertyTypeRequiredDescription
labelstringYesi18n key or display text
pathstringYesURL path
IconReactNodeNoLucide icon component
context'all' | 'personal' | 'organization'NoWhen to show route
childrenRouteChild[]NoNested sub-menu items
endbooleanNoExact path matching for active state

Frequently Asked Questions

How do I add external links to the navigation?
For the sidebar, external links are not directly supported - routes are internal. For marketing header/footer, use standard anchor tags with full URLs.
Can I hide navigation groups based on permissions?
Not declaratively. For permission-based visibility, conditionally build the routes array in the config file by checking the user role.
How do I highlight the active route?
Active state is automatic based on path matching. Use end: true for exact matching when a parent path is a prefix of child paths.
Where do I add admin navigation?
Admin navigation is defined in packages/admin/src/admin-sidebar.tsx. It follows a similar pattern with a config object containing routes, but lives directly in the component file rather than a separate config.

Next: Authentication →