Team Account Navigation Configuration in the Next.js Supabase SaaS Kit
Configure the team account sidebar navigation, layout style, and menu structure in the Next.js Supabase SaaS Kit for B2B team workspaces.
The team account navigation at apps/web/config/team-account-navigation.config.tsx defines the sidebar menu for team workspaces. This configuration differs from personal navigation because routes include the team slug as a dynamic segment.
Team Context
Team navigation routes use [account] as a placeholder that gets replaced with the actual team slug at runtime (e.g., /home/acme-corp/settings).
Layout Options
| Variable | Options | Default | Description |
|---|---|---|---|
NEXT_PUBLIC_TEAM_NAVIGATION_STYLE | sidebar, header | sidebar | Navigation layout style |
NEXT_PUBLIC_TEAM_SIDEBAR_COLLAPSED | true, false | false | Start with collapsed sidebar |
NEXT_PUBLIC_SIDEBAR_COLLAPSIBLE_STYLE | offcanvas, icon, none | icon | How sidebar collapses |
Sidebar Style (Default)
NEXT_PUBLIC_TEAM_NAVIGATION_STYLE=sidebarHeader Style
NEXT_PUBLIC_TEAM_NAVIGATION_STYLE=headerDefault Configuration
The kit ships with these team routes:
import { CreditCard, LayoutDashboard, Settings, Users } from 'lucide-react';import { NavigationConfigSchema } from '@kit/ui/navigation-schema';import featureFlagsConfig from '~/config/feature-flags.config';import pathsConfig from '~/config/paths.config';const iconClasses = 'w-4';const getRoutes = (account: string) => [ { label: 'common:routes.application', children: [ { label: 'common:routes.dashboard', path: pathsConfig.app.accountHome.replace('[account]', account), Icon: <LayoutDashboard className={iconClasses} />, end: true, }, ], }, { label: 'common:routes.settings', collapsible: false, children: [ { label: 'common:routes.settings', path: createPath(pathsConfig.app.accountSettings, account), Icon: <Settings className={iconClasses} />, }, { label: 'common:routes.members', path: createPath(pathsConfig.app.accountMembers, account), Icon: <Users className={iconClasses} />, }, featureFlagsConfig.enableTeamAccountBilling ? { label: 'common:routes.billing', path: createPath(pathsConfig.app.accountBilling, account), Icon: <CreditCard className={iconClasses} />, } : undefined, ].filter(Boolean), },];export function getTeamAccountSidebarConfig(account: string) { return NavigationConfigSchema.parse({ routes: getRoutes(account), style: process.env.NEXT_PUBLIC_TEAM_NAVIGATION_STYLE, sidebarCollapsed: process.env.NEXT_PUBLIC_TEAM_SIDEBAR_COLLAPSED, sidebarCollapsedStyle: process.env.NEXT_PUBLIC_SIDEBAR_COLLAPSIBLE_STYLE, });}function createPath(path: string, account: string) { return path.replace('[account]', account);}Key Differences from Personal Navigation
| Aspect | Personal Navigation | Team Navigation |
|---|---|---|
| Export | personalAccountNavigationConfig (object) | getTeamAccountSidebarConfig(account) (function) |
| Paths | Static (e.g., /home/settings) | Dynamic (e.g., /home/acme-corp/settings) |
| Context | Single user workspace | Team-specific workspace |
Adding Custom Routes
Simple Route
Add a route to an existing section. Use the createPath helper to inject the team slug:
const getRoutes = (account: string) => [ { label: 'common:routes.application', children: [ { label: 'common:routes.dashboard', path: createPath(pathsConfig.app.accountHome, account), Icon: <LayoutDashboard className={iconClasses} />, end: true, }, { label: 'Projects', path: createPath('/home/[account]/projects', account), Icon: <Folder className={iconClasses} />, }, ], }, // ... rest of routes];New Section
Add a new section for team-specific features:
const getRoutes = (account: string) => [ // ... existing sections { label: 'Workspace', children: [ { label: 'Projects', path: createPath('/home/[account]/projects', account), Icon: <Folder className={iconClasses} />, }, { label: 'Documents', path: createPath('/home/[account]/documents', account), Icon: <FileText className={iconClasses} />, }, { label: 'Integrations', path: createPath('/home/[account]/integrations', account), Icon: <Plug className={iconClasses} />, }, ], },];Conditional Routes
Show routes based on feature flags or team permissions:
const getRoutes = (account: string) => [ { label: 'common:routes.settings', collapsible: false, children: [ { label: 'common:routes.settings', path: createPath(pathsConfig.app.accountSettings, account), Icon: <Settings className={iconClasses} />, }, { label: 'common:routes.members', path: createPath(pathsConfig.app.accountMembers, account), Icon: <Users className={iconClasses} />, }, // Only show billing if enabled featureFlagsConfig.enableTeamAccountBilling ? { label: 'common:routes.billing', path: createPath(pathsConfig.app.accountBilling, account), Icon: <CreditCard className={iconClasses} />, } : undefined, ].filter(Boolean), },];Route Properties
| Property | Type | Required | Description |
|---|---|---|---|
label | string | Yes | Display text (supports i18n keys) |
path | string | Yes | Route path with team slug |
Icon | ReactNode | No | Lucide icon component |
end | boolean | No | Exact path matching (for index routes) |
children | Route[] | No | Nested routes for sub-menus |
collapsible | boolean | No | Whether section can collapse |
Using the createPath Helper
Always use the createPath helper to replace [account] with the team slug:
function createPath(path: string, account: string) { return path.replace('[account]', account);}// Usageconst settingsPath = createPath('/home/[account]/settings', 'acme-corp');// Result: '/home/acme-corp/settings'For paths defined in pathsConfig, use the same pattern:
createPath(pathsConfig.app.accountSettings, account)// Converts '/home/[account]/settings' to '/home/acme-corp/settings'Non-Collapsible Sections
Set collapsible: false to keep a section always expanded:
{ label: 'common:routes.settings', collapsible: false, // Always expanded children: [ // ... routes ],}Best Practices
- Always use
createPath: Never hardcode team slugs. Always use the helper function. - Keep paths in
pathsConfig: When adding new routes, add them topaths.config.tsfirst. - Filter undefined routes: When using conditional routes, always add
.filter(Boolean). - Use the
endproperty: Addend: trueto index routes to prevent matching nested paths. - Consider mobile: Test navigation on mobile devices. Complex nested menus can be hard to navigate.
Common Pitfalls
- Forgetting to replace
[account]: If paths show[account]literally in the URL, you forgot to usecreatePath. - Not exporting as function: Team navigation must be a function that accepts the account slug, not a static object.
- Mixing personal and team routes: Team routes should use
/home/[account]/..., personal routes use/home/.... - Hardcoding team slugs: Never hardcode a specific team slug. Always use the
accountparameter.
Related Topics
- Personal Account Navigation - Configure personal navigation
- Paths Configuration - Centralized path management
- Team Accounts - Understanding team workspaces
- Feature Flags - Toggle team features