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.
Where to Add Routes
This is the file you'll edit most often when building your product. Add dashboard pages and feature-specific navigation items here. Settings-section items live in a separate config — see Settings Navigation.
Layout Options
| Variable | Options | Default | Description |
|---|---|---|---|
VITE_USER_NAVIGATION_STYLE | sidebar, header | sidebar | Navigation layout style |
VITE_HOME_SIDEBAR_COLLAPSED | true, false | false | Start with collapsed sidebar |
VITE_SIDEBAR_COLLAPSIBLE_STYLE | offcanvas, icon, none | icon | How sidebar collapses |
Sidebar Style (Default)
VITE_USER_NAVIGATION_STYLE=sidebarShows a vertical sidebar on the left with expandable sections.
Header Style
VITE_USER_NAVIGATION_STYLE=headerShows navigation in a horizontal header bar.
Collapse Behavior
Control how the sidebar behaves when collapsed:
# Icon mode: Shows icons only when collapsedVITE_SIDEBAR_COLLAPSIBLE_STYLE=icon# Offcanvas mode: Slides in/out as an overlayVITE_SIDEBAR_COLLAPSIBLE_STYLE=offcanvas# None: Sidebar cannot be collapsedVITE_SIDEBAR_COLLAPSIBLE_STYLE=noneDefault 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
| Property | Type | Required | Description |
|---|---|---|---|
label | string | Yes | Display text (supports i18n keys) |
path | string | Yes | Route path (static — never encode an account slug) |
Icon | ReactNode | No | Lucide icon component |
highlightMatch | string | No | Regex pattern for active route highlighting |
children | Route[] | No | Nested 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
- Use
pathsConfig: Import paths from the configuration instead of hardcoding strings. - Keep paths static: the active account is DB-resolved — never put an account slug in a path.
- Group logically: Put related routes in the same section.
- Consistent icons: Use icons from
lucide-react(w-4) for visual consistency.
Common Pitfalls
- Missing
highlightMatch: the dashboard route needshighlightMatch: '/dashboard$'so it does not stay highlighted on every/dashboard/*sub-route. - Encoding a slug: there is no
[account]placeholder anymore — resolve the active account fromuseWorkspace()instead. - Wrong icon size: use
w-4to match the navigation style.
Related Topics
- Settings Navigation - Configure the settings sidebar
- Paths Configuration - Centralized path management