Application Navigation in Tanstack Start Supabase

Configure the main application sidebar navigation, layout style, and menu structure in the Tanstack Start Supabase SaaS Kit.

The application navigation at apps/web/config/navigation.config.tsx defines the main sidebar menu for the dashboard shell. A single, slug-free config serves both personal and team accounts — the active account is resolved from the database, so the same routes work in every workspace. Add your own routes here to extend the dashboard navigation.

Layout Options

VariableOptionsDefaultDescription
VITE_USER_NAVIGATION_STYLEsidebar, headersidebarNavigation layout style
VITE_HOME_SIDEBAR_COLLAPSEDtrue, falsefalseStart with collapsed sidebar
VITE_SIDEBAR_COLLAPSIBLE_STYLEoffcanvas, icon, noneiconHow sidebar collapses
VITE_USER_NAVIGATION_STYLE=sidebar

Shows a vertical sidebar on the left with expandable sections.

Header Style

VITE_USER_NAVIGATION_STYLE=header

Shows navigation in a horizontal header bar.

Collapse Behavior

Control how the sidebar behaves when collapsed:

# Icon mode: Shows icons only when collapsed
VITE_SIDEBAR_COLLAPSIBLE_STYLE=icon
# Offcanvas mode: Slides in/out as an overlay
VITE_SIDEBAR_COLLAPSIBLE_STYLE=offcanvas
# None: Sidebar cannot be collapsed
VITE_SIDEBAR_COLLAPSIBLE_STYLE=none

Default Configuration

The kit ships with a single dashboard entry:

import { Home } from 'lucide-react';
import * as z from 'zod';
import { NavigationConfigSchema } from '@kit/ui/navigation-schema';
import pathsConfig from '#/config/paths.config.ts';
const iconClasses = 'w-4';
const routes = [
{
label: 'common.routes.application',
children: [
{
label: 'common.routes.dashboard',
path: pathsConfig.app.home,
Icon: <Home className={iconClasses} />,
highlightMatch: `${pathsConfig.app.home}$`,
},
],
},
] satisfies z.output<typeof NavigationConfigSchema>['routes'];
export const workspaceNavigationConfig = NavigationConfigSchema.parse({
routes,
style: import.meta.env.VITE_USER_NAVIGATION_STYLE,
sidebarCollapsed: import.meta.env.VITE_HOME_SIDEBAR_COLLAPSED,
sidebarCollapsedStyle: import.meta.env.VITE_SIDEBAR_COLLAPSIBLE_STYLE,
});

Adding Custom Routes

Simple Route

Add a route to an existing section:

const routes = [
{
label: 'common.routes.application',
children: [
{
label: 'common.routes.dashboard',
path: pathsConfig.app.home,
Icon: <Home className={iconClasses} />,
highlightMatch: `${pathsConfig.app.home}$`,
},
{
label: 'Projects',
path: '/dashboard/projects',
Icon: <Folder className={iconClasses} />,
},
],
},
];

New Section

Add a new collapsible section:

const routes = [
// ... existing sections
{
label: 'Your Store',
children: [
{
label: 'Products',
path: '/dashboard/products',
Icon: <Package className={iconClasses} />,
},
{
label: 'Orders',
path: '/dashboard/orders',
Icon: <ShoppingCart className={iconClasses} />,
},
],
},
];

Account-aware Routes

Because a single config serves both personal and team accounts, gate any account-specific items at render time using the active account rather than the URL:

import { useWorkspace } from '#/components/workspace-context.tsx';
function useNav() {
const { account } = useWorkspace();
// e.g. only show a team-only item when a team is active
return account.is_personal_account ? personalRoutes : teamRoutes;
}

Route Properties

PropertyTypeRequiredDescription
labelstringYesDisplay text (supports i18n keys)
pathstringYesRoute path (static — never encode an account slug)
IconReactNodeNoLucide icon component
highlightMatchstringNoRegex pattern for active route highlighting
childrenRoute[]NoNested routes for sub-menus

Internationalization

Route labels support i18n keys:

{
label: 'common.routes.dashboard', // Uses translation
path: pathsConfig.app.home,
Icon: <Home className={iconClasses} />,
}

Translation files are in apps/web/i18n/messages/[locale]/common.json:

{
"routes": {
"dashboard": "Dashboard",
"settings": "Settings",
"profile": "Profile"
}
}

Best Practices

  1. Use pathsConfig: Import paths from the configuration instead of hardcoding strings.
  2. Keep paths static: the active account is DB-resolved — never put an account slug in a path.
  3. Group logically: Put related routes in the same section.
  4. Consistent icons: Use icons from lucide-react (w-4) for visual consistency.

Common Pitfalls

  1. Missing highlightMatch: the dashboard route needs highlightMatch: '/dashboard$' so it does not stay highlighted on every /dashboard/* sub-route.
  2. Encoding a slug: there is no [account] placeholder anymore — resolve the active account from useWorkspace() instead.
  3. Wrong icon size: use w-4 to match the navigation style.