Next.js Supabase V3 (#463)

Version 3 of the kit:
- Radix UI replaced with Base UI (using the Shadcn UI patterns)
- next-intl replaces react-i18next
- enhanceAction deprecated; usage moved to next-safe-action
- main layout now wrapped with [locale] path segment
- Teams only mode
- Layout updates
- Zod v4
- Next.js 16.2
- Typescript 6
- All other dependencies updated
- Removed deprecated Edge CSRF
- Dynamic Github Action runner
This commit is contained in:
Giancarlo Buomprisco
2026-03-24 13:40:38 +08:00
committed by GitHub
parent 4912e402a3
commit 7ebff31475
840 changed files with 71395 additions and 20095 deletions

View File

@@ -0,0 +1,133 @@
---
status: "published"
label: "Application Configuration"
title: "Application Configuration in the Next.js Supabase SaaS Kit"
description: "Configure your app name, URL, theme colors, and locale settings in the Next.js Supabase SaaS Kit using environment variables."
order: 1
---
The application configuration at `apps/web/config/app.config.ts` defines your SaaS application's core settings: name, URL, theme, and locale. Configure these using environment variables rather than editing the file directly.
{% alert type="default" title="Configuration Approach" %}
All configuration is driven by environment variables and validated with Zod at build time. Invalid configuration fails the build immediately, preventing deployment of broken settings.
{% /alert %}
## Configuration Options
| Variable | Required | Description |
|----------|----------|-------------|
| `NEXT_PUBLIC_PRODUCT_NAME` | Yes | Your product name (e.g., "Acme SaaS") |
| `NEXT_PUBLIC_SITE_TITLE` | Yes | Browser title tag and SEO title |
| `NEXT_PUBLIC_SITE_DESCRIPTION` | Yes | Meta description for SEO |
| `NEXT_PUBLIC_SITE_URL` | Yes | Full URL with protocol (e.g., `https://myapp.com`) |
| `NEXT_PUBLIC_DEFAULT_LOCALE` | No | Default language code (default: `en`) |
| `NEXT_PUBLIC_DEFAULT_THEME_MODE` | No | Theme: `light`, `dark`, or `system` |
| `NEXT_PUBLIC_THEME_COLOR` | Yes | Light theme color (hex, e.g., `#ffffff`) |
| `NEXT_PUBLIC_THEME_COLOR_DARK` | Yes | Dark theme color (hex, e.g., `#0a0a0a`) |
## Basic Setup
Add these to your `.env` file:
```bash
NEXT_PUBLIC_SITE_URL=https://myapp.com
NEXT_PUBLIC_PRODUCT_NAME="My SaaS App"
NEXT_PUBLIC_SITE_TITLE="My SaaS App - Build Faster"
NEXT_PUBLIC_SITE_DESCRIPTION="The easiest way to build your SaaS application"
NEXT_PUBLIC_DEFAULT_LOCALE=en
NEXT_PUBLIC_DEFAULT_THEME_MODE=light
NEXT_PUBLIC_THEME_COLOR="#ffffff"
NEXT_PUBLIC_THEME_COLOR_DARK="#0a0a0a"
```
## How It Works
The configuration file parses environment variables through a Zod schema:
```typescript
const appConfig = AppConfigSchema.parse({
name: process.env.NEXT_PUBLIC_PRODUCT_NAME,
title: process.env.NEXT_PUBLIC_SITE_TITLE,
description: process.env.NEXT_PUBLIC_SITE_DESCRIPTION,
url: process.env.NEXT_PUBLIC_SITE_URL,
locale: process.env.NEXT_PUBLIC_DEFAULT_LOCALE,
theme: process.env.NEXT_PUBLIC_DEFAULT_THEME_MODE,
themeColor: process.env.NEXT_PUBLIC_THEME_COLOR,
themeColorDark: process.env.NEXT_PUBLIC_THEME_COLOR_DARK,
production,
});
```
## Environment-Specific Configuration
Structure your environment files for different deployment stages:
| File | Purpose |
|------|---------|
| `.env` | Shared settings across all environments |
| `.env.development` | Local development overrides |
| `.env.production` | Production-specific settings |
| `.env.local` | Local secrets (git-ignored) |
**Example for development:**
```bash
# .env.development
NEXT_PUBLIC_SITE_URL=http://localhost:3000
```
**Example for production:**
```bash
# .env.production (or CI/CD environment)
NEXT_PUBLIC_SITE_URL=https://myapp.com
```
## Common Configuration Scenarios
### B2C SaaS Application
```bash
NEXT_PUBLIC_PRODUCT_NAME="PhotoEdit Pro"
NEXT_PUBLIC_SITE_TITLE="PhotoEdit Pro - Edit Photos Online"
NEXT_PUBLIC_SITE_DESCRIPTION="Professional photo editing in your browser. No download required."
NEXT_PUBLIC_SITE_URL=https://photoedit.pro
NEXT_PUBLIC_DEFAULT_THEME_MODE=system
```
### B2B SaaS Application
```bash
NEXT_PUBLIC_PRODUCT_NAME="TeamFlow"
NEXT_PUBLIC_SITE_TITLE="TeamFlow - Project Management for Teams"
NEXT_PUBLIC_SITE_DESCRIPTION="Streamline your team's workflow with powerful project management tools."
NEXT_PUBLIC_SITE_URL=https://teamflow.io
NEXT_PUBLIC_DEFAULT_THEME_MODE=light
```
## Common Pitfalls
1. **HTTP in production**: The build fails if `NEXT_PUBLIC_SITE_URL` uses `http://` in production. Always use `https://` for production deployments.
2. **Same theme colors**: The build fails if `NEXT_PUBLIC_THEME_COLOR` equals `NEXT_PUBLIC_THEME_COLOR_DARK`. They must be different values.
3. **Missing trailing slash**: Don't include a trailing slash in `NEXT_PUBLIC_SITE_URL`. Use `https://myapp.com` not `https://myapp.com/`.
4. **Forgetting to rebuild**: Environment variable changes require a rebuild. Run `pnpm build` after changing production values.
## Accessing Configuration in Code
Import the configuration anywhere in your application:
```typescript
import appConfig from '~/config/app.config';
// Access values
console.log(appConfig.name); // "My SaaS App"
console.log(appConfig.url); // "https://myapp.com"
console.log(appConfig.theme); // "light"
console.log(appConfig.production); // true/false
```
## Related Topics
- [Environment Variables](/docs/next-supabase-turbo/configuration/environment-variables) - Complete environment variable reference
- [Feature Flags](/docs/next-supabase-turbo/configuration/feature-flags-configuration) - Enable or disable features
- [Going to Production](/docs/next-supabase-turbo/going-to-production/checklist) - Production deployment checklist

View File

@@ -0,0 +1,206 @@
---
status: "published"
label: "Authentication Configuration"
title: "Authentication Configuration: Password, Magic Link, OAuth, MFA"
description: "Configure email/password, magic link, OTP, and OAuth authentication in the Next.js Supabase SaaS Kit. Set up password requirements, identity linking, and CAPTCHA protection."
order: 2
---
The authentication configuration at `apps/web/config/auth.config.ts` controls which sign-in methods are available and how they behave. Configure using environment variables to enable password, magic link, OTP, or OAuth authentication.
{% alert type="default" title="Quick Setup" %}
Password authentication is enabled by default. To switch to magic link or add OAuth providers, set the corresponding environment variables and configure the providers in your Supabase Dashboard.
{% /alert %}
## Authentication Methods
| Method | Environment Variable | Default | Description |
|--------|---------------------|---------|-------------|
| Password | `NEXT_PUBLIC_AUTH_PASSWORD` | `true` | Traditional email/password |
| Magic Link | `NEXT_PUBLIC_AUTH_MAGIC_LINK` | `false` | Passwordless email links |
| OTP | `NEXT_PUBLIC_AUTH_OTP` | `false` | One-time password codes |
| OAuth | Configure in code | `['google']` | Third-party providers |
## Basic Configuration
```bash
# Enable password authentication (default)
NEXT_PUBLIC_AUTH_PASSWORD=true
NEXT_PUBLIC_AUTH_MAGIC_LINK=false
NEXT_PUBLIC_AUTH_OTP=false
```
## Switching to Magic Link
```bash
NEXT_PUBLIC_AUTH_PASSWORD=false
NEXT_PUBLIC_AUTH_MAGIC_LINK=true
```
## Switching to OTP
```bash
NEXT_PUBLIC_AUTH_PASSWORD=false
NEXT_PUBLIC_AUTH_OTP=true
```
When using OTP, update your Supabase email templates in `apps/web/supabase/config.toml`:
```toml
[auth.email.template.confirmation]
subject = "Confirm your email"
content_path = "./supabase/templates/otp.html"
[auth.email.template.magic_link]
subject = "Sign in to Makerkit"
content_path = "./supabase/templates/otp.html"
```
Also update the templates in your Supabase Dashboard under **Authentication > Templates** for production.
## OAuth Providers
### Supported Providers
The kit supports all Supabase OAuth providers:
| Provider | ID | Provider | ID |
|----------|-----|----------|-----|
| Apple | `apple` | Kakao | `kakao` |
| Azure | `azure` | Keycloak | `keycloak` |
| Bitbucket | `bitbucket` | LinkedIn | `linkedin` |
| Discord | `discord` | LinkedIn OIDC | `linkedin_oidc` |
| Facebook | `facebook` | Notion | `notion` |
| Figma | `figma` | Slack | `slack` |
| GitHub | `github` | Spotify | `spotify` |
| GitLab | `gitlab` | Twitch | `twitch` |
| Google | `google` | Twitter | `twitter` |
| Fly | `fly` | WorkOS | `workos` |
| | | Zoom | `zoom` |
### Configuring OAuth Providers
OAuth providers are configured in two places:
1. **Supabase Dashboard**: Enable and configure credentials (Client ID, Client Secret)
2. **Code**: Display in the sign-in UI
Edit `apps/web/config/auth.config.ts` to change which providers appear:
```typescript
providers: {
password: process.env.NEXT_PUBLIC_AUTH_PASSWORD === 'true',
magicLink: process.env.NEXT_PUBLIC_AUTH_MAGIC_LINK === 'true',
otp: process.env.NEXT_PUBLIC_AUTH_OTP === 'true',
oAuth: ['google', 'github'], // Add providers here
}
```
{% alert type="warning" title="Provider Configuration" %}
Adding a provider to the array only displays it in the UI. You must also configure the provider in your Supabase Dashboard with valid credentials. See [Supabase's OAuth documentation](https://supabase.com/docs/guides/auth/social-login).
{% /alert %}
### OAuth Scopes
Some providers require specific scopes. Configure them in `packages/features/auth/src/components/oauth-providers.tsx`:
```tsx
const OAUTH_SCOPES: Partial<Record<Provider, string>> = {
azure: 'email',
keycloak: 'openid',
// add your OAuth providers here
};
```
The kit ships with Azure and Keycloak scopes configured. Add additional providers as needed based on their OAuth requirements.
### Local Development OAuth
For local OAuth testing, configure your providers in `apps/web/supabase/config.toml`. See [Supabase's local development OAuth guide](https://supabase.com/docs/guides/local-development/managing-config).
## Identity Linking
Allow users to link multiple authentication methods (e.g., link Google to an existing email account):
```bash
NEXT_PUBLIC_AUTH_IDENTITY_LINKING=true
```
This must also be enabled in your Supabase Dashboard under **Authentication > Settings**.
## Password Requirements
Enforce password strength rules:
```bash
NEXT_PUBLIC_PASSWORD_REQUIRE_UPPERCASE=true
NEXT_PUBLIC_PASSWORD_REQUIRE_NUMBERS=true
NEXT_PUBLIC_PASSWORD_REQUIRE_SPECIAL_CHARS=true
```
These rules validate:
1. At least one uppercase letter
2. At least one number
3. At least one special character
## CAPTCHA Protection
Protect authentication forms with Cloudflare Turnstile:
```bash
NEXT_PUBLIC_CAPTCHA_SITE_KEY=your-site-key
CAPTCHA_SECRET_TOKEN=your-secret-token
```
Get your keys from the [Cloudflare Turnstile dashboard](https://dash.cloudflare.com/?to=/:account/turnstile).
## Terms and Conditions
Display a terms checkbox during sign-up:
```bash
NEXT_PUBLIC_DISPLAY_TERMS_AND_CONDITIONS_CHECKBOX=true
```
## MFA (Multi-Factor Authentication)
MFA is built into Supabase Auth. To enforce MFA for specific operations:
1. Enable MFA in your Supabase Dashboard
2. Customize RLS policies per [Supabase's MFA documentation](https://supabase.com/blog/mfa-auth-via-rls)
The super admin dashboard already requires MFA for access.
## How It Works
The configuration file parses environment variables through a Zod schema:
```typescript
const authConfig = AuthConfigSchema.parse({
captchaTokenSiteKey: process.env.NEXT_PUBLIC_CAPTCHA_SITE_KEY,
displayTermsCheckbox:
process.env.NEXT_PUBLIC_DISPLAY_TERMS_AND_CONDITIONS_CHECKBOX === 'true',
enableIdentityLinking:
process.env.NEXT_PUBLIC_AUTH_IDENTITY_LINKING === 'true',
providers: {
password: process.env.NEXT_PUBLIC_AUTH_PASSWORD === 'true',
magicLink: process.env.NEXT_PUBLIC_AUTH_MAGIC_LINK === 'true',
otp: process.env.NEXT_PUBLIC_AUTH_OTP === 'true',
oAuth: ['google'],
},
});
```
## Common Pitfalls
1. **OAuth not working**: Ensure the provider is configured in both the code and Supabase Dashboard with matching credentials.
2. **Magic link emails not arriving**: Check your email configuration and Supabase email templates. For local development, emails go to Mailpit at `localhost:54324`.
3. **OTP using wrong template**: Both OTP and magic link use the same Supabase email template type. Use `otp.html` for OTP or `magic-link.html` for magic links, but not both simultaneously.
4. **Identity linking fails**: Must be enabled in both environment variables and Supabase Dashboard.
## Related Topics
- [Authentication API](/docs/next-supabase-turbo/api/authentication-api) - Check user authentication status in code
- [Production Authentication](/docs/next-supabase-turbo/going-to-production/authentication) - Configure authentication for production
- [Authentication Emails](/docs/next-supabase-turbo/emails/authentication-emails) - Customize email templates
- [Environment Variables](/docs/next-supabase-turbo/configuration/environment-variables) - Complete variable reference

View File

@@ -0,0 +1,305 @@
---
status: "published"
title: "Environment Variables Reference for the Next.js Supabase SaaS Kit"
label: "Environment Variables"
order: 0
description: "Complete reference for all environment variables in the Next.js Supabase SaaS Kit, including Supabase, Stripe, email, and feature flag configuration."
---
This page documents all environment variables used by the Next.js Supabase SaaS Kit. Variables are organized by category and include their purpose, required status, and default values.
## Environment File Structure
| File | Purpose | Git Status |
|------|---------|------------|
| `.env` | Shared settings across all environments | Committed |
| `.env.development` | Development-specific overrides | Committed |
| `.env.production` | Production-specific settings | Committed |
| `.env.local` | Local secrets and overrides | Git-ignored |
**Priority order**: `.env.local` > `.env.development`/`.env.production` > `.env`
## Required Variables
These variables must be set for the application to start:
```bash
# Supabase (required)
NEXT_PUBLIC_SUPABASE_URL=https://yourproject.supabase.co
NEXT_PUBLIC_SUPABASE_PUBLIC_KEY=your-public-key
SUPABASE_SECRET_KEY=your-service-role-key
# App identity (required)
NEXT_PUBLIC_SITE_URL=https://yourapp.com
NEXT_PUBLIC_PRODUCT_NAME=Your Product
NEXT_PUBLIC_SITE_TITLE="Your Product - Tagline"
NEXT_PUBLIC_SITE_DESCRIPTION="Your product description"
```
## Core Configuration
### Site Identity
```bash
NEXT_PUBLIC_SITE_URL=https://example.com
NEXT_PUBLIC_PRODUCT_NAME=Makerkit
NEXT_PUBLIC_SITE_TITLE="Makerkit - Build SaaS Faster"
NEXT_PUBLIC_SITE_DESCRIPTION="Production-ready SaaS starter kit"
NEXT_PUBLIC_DEFAULT_LOCALE=en
```
| Variable | Required | Description |
|----------|----------|-------------|
| `NEXT_PUBLIC_SITE_URL` | Yes | Full URL with protocol |
| `NEXT_PUBLIC_PRODUCT_NAME` | Yes | Product name shown in UI |
| `NEXT_PUBLIC_SITE_TITLE` | Yes | Browser title and SEO |
| `NEXT_PUBLIC_SITE_DESCRIPTION` | Yes | Meta description |
| `NEXT_PUBLIC_DEFAULT_LOCALE` | No | Default language (default: `en`) |
### Theme
```bash
NEXT_PUBLIC_DEFAULT_THEME_MODE=light
NEXT_PUBLIC_THEME_COLOR="#ffffff"
NEXT_PUBLIC_THEME_COLOR_DARK="#0a0a0a"
NEXT_PUBLIC_ENABLE_THEME_TOGGLE=true
```
| Variable | Options | Default | Description |
|----------|---------|---------|-------------|
| `NEXT_PUBLIC_DEFAULT_THEME_MODE` | `light`, `dark`, `system` | `light` | Initial theme |
| `NEXT_PUBLIC_THEME_COLOR` | Hex color | Required | Light theme color |
| `NEXT_PUBLIC_THEME_COLOR_DARK` | Hex color | Required | Dark theme color |
| `NEXT_PUBLIC_ENABLE_THEME_TOGGLE` | `true`, `false` | `true` | Allow theme switching |
## Supabase Configuration
```bash
NEXT_PUBLIC_SUPABASE_URL=https://yourproject.supabase.co
NEXT_PUBLIC_SUPABASE_PUBLIC_KEY=your-public-key
SUPABASE_SECRET_KEY=your-service-role-key
SUPABASE_DB_WEBHOOK_SECRET=your-webhook-secret
```
| Variable | Required | Description |
|----------|----------|-------------|
| `NEXT_PUBLIC_SUPABASE_URL` | Yes | Supabase project URL |
| `NEXT_PUBLIC_SUPABASE_PUBLIC_KEY` | Yes | Public anon key |
| `SUPABASE_SECRET_KEY` | Yes | Service role key (keep secret) |
| `SUPABASE_DB_WEBHOOK_SECRET` | No | Webhook verification secret |
{% alert type="warning" title="Legacy Key Names" %}
If you're using a version prior to 2.12.0, use `NEXT_PUBLIC_SUPABASE_ANON_KEY` and `SUPABASE_SERVICE_ROLE_KEY` instead.
{% /alert %}
## Authentication
```bash
NEXT_PUBLIC_AUTH_PASSWORD=true
NEXT_PUBLIC_AUTH_MAGIC_LINK=false
NEXT_PUBLIC_AUTH_OTP=false
NEXT_PUBLIC_AUTH_IDENTITY_LINKING=false
NEXT_PUBLIC_CAPTCHA_SITE_KEY=
CAPTCHA_SECRET_TOKEN=
NEXT_PUBLIC_DISPLAY_TERMS_AND_CONDITIONS_CHECKBOX=false
```
| Variable | Default | Description |
|----------|---------|-------------|
| `NEXT_PUBLIC_AUTH_PASSWORD` | `true` | Enable password auth |
| `NEXT_PUBLIC_AUTH_MAGIC_LINK` | `false` | Enable magic link auth |
| `NEXT_PUBLIC_AUTH_OTP` | `false` | Enable OTP auth |
| `NEXT_PUBLIC_AUTH_IDENTITY_LINKING` | `false` | Allow identity linking |
| `NEXT_PUBLIC_CAPTCHA_SITE_KEY` | - | Cloudflare Turnstile site key |
| `CAPTCHA_SECRET_TOKEN` | - | Cloudflare Turnstile secret |
| `NEXT_PUBLIC_DISPLAY_TERMS_AND_CONDITIONS_CHECKBOX` | `false` | Show terms checkbox |
### Password Requirements
```bash
NEXT_PUBLIC_PASSWORD_REQUIRE_UPPERCASE=false
NEXT_PUBLIC_PASSWORD_REQUIRE_NUMBERS=false
NEXT_PUBLIC_PASSWORD_REQUIRE_SPECIAL_CHARS=false
```
## Navigation and Layout
```bash
NEXT_PUBLIC_USER_NAVIGATION_STYLE=sidebar
NEXT_PUBLIC_HOME_SIDEBAR_COLLAPSED=false
NEXT_PUBLIC_TEAM_NAVIGATION_STYLE=sidebar
NEXT_PUBLIC_TEAM_SIDEBAR_COLLAPSED=false
NEXT_PUBLIC_SIDEBAR_COLLAPSIBLE_STYLE=icon
NEXT_PUBLIC_ENABLE_SIDEBAR_TRIGGER=true
```
| Variable | Options | Default | Description |
|----------|---------|---------|-------------|
| `NEXT_PUBLIC_USER_NAVIGATION_STYLE` | `sidebar`, `header` | `sidebar` | Personal nav layout |
| `NEXT_PUBLIC_HOME_SIDEBAR_COLLAPSED` | `true`, `false` | `false` | Start collapsed |
| `NEXT_PUBLIC_TEAM_NAVIGATION_STYLE` | `sidebar`, `header` | `sidebar` | Team nav layout |
| `NEXT_PUBLIC_TEAM_SIDEBAR_COLLAPSED` | `true`, `false` | `false` | Start collapsed |
| `NEXT_PUBLIC_SIDEBAR_COLLAPSIBLE_STYLE` | `offcanvas`, `icon`, `none` | `icon` | Collapse behavior |
| `NEXT_PUBLIC_ENABLE_SIDEBAR_TRIGGER` | `true`, `false` | `true` | Show collapse button |
## Feature Flags
```bash
NEXT_PUBLIC_ENABLE_THEME_TOGGLE=true
NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_DELETION=false
NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_BILLING=false
NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS=true
NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_CREATION=true
NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_DELETION=false
NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_BILLING=false
NEXT_PUBLIC_ENABLE_NOTIFICATIONS=true
NEXT_PUBLIC_REALTIME_NOTIFICATIONS=false
NEXT_PUBLIC_ENABLE_VERSION_UPDATER=false
NEXT_PUBLIC_LANGUAGE_PRIORITY=application
```
| Variable | Default | Description |
|----------|---------|-------------|
| `NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_DELETION` | `false` | Users can delete accounts |
| `NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_BILLING` | `false` | Personal subscription billing |
| `NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS` | `true` | Enable team features |
| `NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_CREATION` | `true` | Users can create teams |
| `NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_DELETION` | `false` | Users can delete teams |
| `NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_BILLING` | `false` | Team subscription billing |
| `NEXT_PUBLIC_ENABLE_NOTIFICATIONS` | `true` | In-app notifications |
| `NEXT_PUBLIC_REALTIME_NOTIFICATIONS` | `false` | Live notification updates |
| `NEXT_PUBLIC_ENABLE_VERSION_UPDATER` | `false` | Check for updates |
| `NEXT_PUBLIC_LANGUAGE_PRIORITY` | `application` | `user` or `application` |
## Billing Configuration
### Provider Selection
```bash
NEXT_PUBLIC_BILLING_PROVIDER=stripe
```
Options: `stripe` or `lemon-squeezy`
### Stripe
```bash
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
```
| Variable | Required | Description |
|----------|----------|-------------|
| `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` | Yes (Stripe) | Publishable key |
| `STRIPE_SECRET_KEY` | Yes (Stripe) | Secret key |
| `STRIPE_WEBHOOK_SECRET` | Yes (Stripe) | Webhook signing secret |
### Lemon Squeezy
```bash
LEMON_SQUEEZY_SECRET_KEY=your-secret-key
LEMON_SQUEEZY_STORE_ID=your-store-id
LEMON_SQUEEZY_SIGNING_SECRET=your-signing-secret
```
| Variable | Required | Description |
|----------|----------|-------------|
| `LEMON_SQUEEZY_SECRET_KEY` | Yes (LS) | API secret key |
| `LEMON_SQUEEZY_STORE_ID` | Yes (LS) | Store identifier |
| `LEMON_SQUEEZY_SIGNING_SECRET` | Yes (LS) | Webhook signing secret |
## Email Configuration
### Provider Selection
```bash
MAILER_PROVIDER=nodemailer
```
Options: `nodemailer` or `resend`
### Common Settings
```bash
EMAIL_SENDER="Your App <noreply@yourapp.com>"
CONTACT_EMAIL=contact@yourapp.com
```
### Resend
```bash
RESEND_API_KEY=re_...
```
### Nodemailer (SMTP)
```bash
EMAIL_HOST=smtp.provider.com
EMAIL_PORT=587
EMAIL_USER=your-username
EMAIL_PASSWORD=your-password
EMAIL_TLS=true
```
## CMS Configuration
### Provider Selection
```bash
CMS_CLIENT=keystatic
```
Options: `keystatic` or `wordpress`
### Keystatic
```bash
NEXT_PUBLIC_KEYSTATIC_STORAGE_KIND=local
NEXT_PUBLIC_KEYSTATIC_CONTENT_PATH=./content
KEYSTATIC_PATH_PREFIX=apps/web
```
For GitHub storage:
```bash
NEXT_PUBLIC_KEYSTATIC_STORAGE_KIND=github
NEXT_PUBLIC_KEYSTATIC_STORAGE_REPO=owner/repo
KEYSTATIC_GITHUB_TOKEN=github_pat_...
```
| Variable | Options | Description |
|----------|---------|-------------|
| `NEXT_PUBLIC_KEYSTATIC_STORAGE_KIND` | `local`, `cloud`, `github` | Storage backend |
| `NEXT_PUBLIC_KEYSTATIC_CONTENT_PATH` | Path | Content directory |
| `KEYSTATIC_PATH_PREFIX` | Path | Monorepo prefix |
| `NEXT_PUBLIC_KEYSTATIC_STORAGE_REPO` | `owner/repo` | GitHub repository |
| `KEYSTATIC_GITHUB_TOKEN` | Token | GitHub access token |
### WordPress
```bash
WORDPRESS_API_URL=https://your-site.com/wp-json
```
## Security Best Practices
1. **Never commit secrets**: Use `.env.local` for sensitive values
2. **Use CI/CD variables**: Store production secrets in your deployment platform
3. **Rotate keys regularly**: Especially after team member changes
4. **Validate in production**: The kit validates configuration at build time
## Common Pitfalls
1. **HTTP in production**: `NEXT_PUBLIC_SITE_URL` must use `https://` in production builds.
2. **Same theme colors**: `NEXT_PUBLIC_THEME_COLOR` and `NEXT_PUBLIC_THEME_COLOR_DARK` must be different.
3. **Missing Supabase keys**: The app won't start without valid Supabase credentials.
4. **Forgetting to restart**: After changing environment variables, you may need to restart the development server.
5. **Wrong file for secrets**: Put secrets in `.env.local` (git-ignored), not `.env` (committed).
## Related Topics
- [Application Configuration](/docs/next-supabase-turbo/configuration/application-configuration) - Core app settings
- [Authentication Configuration](/docs/next-supabase-turbo/configuration/authentication-configuration) - Auth setup
- [Feature Flags](/docs/next-supabase-turbo/configuration/feature-flags-configuration) - Toggle features
- [Going to Production](/docs/next-supabase-turbo/going-to-production/checklist) - Deployment checklist

View File

@@ -0,0 +1,260 @@
---
status: "published"
label: "Feature Flags"
title: "Feature Flags Configuration in the Next.js Supabase SaaS Kit"
description: "Enable or disable team accounts, billing, notifications, and theme toggling in the Next.js Supabase SaaS Kit using feature flags."
order: 4
---
The feature flags configuration at `apps/web/config/feature-flags.config.ts` controls which features are enabled in your application. Toggle team accounts, billing, notifications, and more using environment variables.
{% alert type="default" title="Feature Flags vs Configuration" %}
Feature flags control whether functionality is available to users. Use them to ship different product tiers, run A/B tests, or disable features during maintenance. Unlike configuration, feature flags are meant to change at runtime or between deployments.
{% /alert %}
{% alert type="warning" title="Defaults Note" %}
The "Default" column shows what the code uses if the environment variable is not set. The kit's `.env` file ships with different values to demonstrate features. Check your `.env` file for the actual starting values.
{% /alert %}
## Available Feature Flags
| Flag | Environment Variable | Default | Description |
|------|---------------------|---------|-------------|
| Theme Toggle | `NEXT_PUBLIC_ENABLE_THEME_TOGGLE` | `true` | Allow users to switch themes |
| Account Deletion | `NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_DELETION` | `false` | Users can delete their accounts |
| Team Accounts | `NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS` | `true` | Enable team/organization features |
| Team Creation | `NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_CREATION` | `true` | Users can create new teams |
| Team Deletion | `NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_DELETION` | `false` | Users can delete their teams |
| Personal Billing | `NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_BILLING` | `false` | Billing for personal accounts |
| Team Billing | `NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_BILLING` | `false` | Billing for team accounts |
| Notifications | `NEXT_PUBLIC_ENABLE_NOTIFICATIONS` | `true` | In-app notification system |
| Realtime Notifications | `NEXT_PUBLIC_REALTIME_NOTIFICATIONS` | `false` | Live notification updates |
| Version Updater | `NEXT_PUBLIC_ENABLE_VERSION_UPDATER` | `false` | Check for app updates |
| Teams Only | `NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_ONLY` | `false` | Skip personal accounts, use teams only |
| Language Priority | `NEXT_PUBLIC_LANGUAGE_PRIORITY` | `application` | User vs app language preference |
## Common Configurations
### B2C SaaS (Personal Accounts Only)
For consumer applications where each user has their own account and subscription:
```bash
NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS=false
NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_BILLING=true
NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_DELETION=true
```
### B2B SaaS (Team Accounts Only)
For business applications where organizations subscribe and manage team members. Enable `NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_ONLY` to skip personal accounts entirely:
```bash
NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS=true
NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_ONLY=true
NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_BILLING=true
NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_DELETION=true
NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_BILLING=false
```
When `NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_ONLY=true`:
- Users are automatically redirected away from personal account routes to their team workspace
- The personal account section in the sidebar/workspace switcher is hidden
- After sign-in, users land on their team dashboard instead of a personal home page
- The last selected team is remembered in a cookie so returning users go straight to their team
This is the recommended approach for B2B apps. It removes the personal account layer entirely so users only interact with team workspaces.
### Hybrid Model (Both Personal and Team)
For applications supporting both individual users and teams (uncommon):
```bash
NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS=true
NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_BILLING=true
NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_BILLING=true
```
### Managed Onboarding (No Self-Service Team Creation)
For applications where you create teams on behalf of customers:
```bash
NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS=true
NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_CREATION=false
NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_BILLING=true
```
## Decision Matrix
Use this matrix to decide which flags to enable:
| Use Case | Theme | Teams | Team Creation | Personal Billing | Team Billing | Deletion |
|----------|-------|-------|---------------|------------------|--------------|----------|
| B2C Consumer App | Yes | No | - | Yes | - | Yes |
| B2B Team SaaS | Optional | Yes | Yes | No | Yes | Optional |
| Enterprise SaaS | Optional | Yes | No | No | Yes | No |
| Freemium Personal | Yes | No | - | Yes | - | Yes |
| Marketplace | Optional | Yes | Yes | Yes | No | Yes |
## How It Works
The configuration file parses environment variables with sensible defaults:
```typescript
const featuresFlagConfig = FeatureFlagsSchema.parse({
enableThemeToggle: getBoolean(
process.env.NEXT_PUBLIC_ENABLE_THEME_TOGGLE,
true,
),
enableAccountDeletion: getBoolean(
process.env.NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_DELETION,
false,
),
enableTeamDeletion: getBoolean(
process.env.NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_DELETION,
false,
),
enableTeamAccounts: getBoolean(
process.env.NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS,
true,
),
enableTeamCreation: getBoolean(
process.env.NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_CREATION,
true,
),
enablePersonalAccountBilling: getBoolean(
process.env.NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_BILLING,
false,
),
enableTeamAccountBilling: getBoolean(
process.env.NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_BILLING,
false,
),
languagePriority: process.env.NEXT_PUBLIC_LANGUAGE_PRIORITY,
enableNotifications: getBoolean(
process.env.NEXT_PUBLIC_ENABLE_NOTIFICATIONS,
true,
),
realtimeNotifications: getBoolean(
process.env.NEXT_PUBLIC_REALTIME_NOTIFICATIONS,
false,
),
enableVersionUpdater: getBoolean(
process.env.NEXT_PUBLIC_ENABLE_VERSION_UPDATER,
false,
),
});
```
## Using Feature Flags in Code
### In Server Components
```typescript
import featureFlagsConfig from '~/config/feature-flags.config';
export default function SettingsPage() {
return (
<div>
{featureFlagsConfig.enableTeamAccounts && (
<TeamAccountsSection />
)}
{featureFlagsConfig.enableAccountDeletion && (
<DeleteAccountButton />
)}
</div>
);
}
```
### In Client Components
```tsx
'use client';
import featureFlagsConfig from '~/config/feature-flags.config';
export function ThemeToggle() {
if (!featureFlagsConfig.enableThemeToggle) {
return null;
}
return <ThemeSwitch />;
}
```
### Conditional Navigation
The navigation configuration files already use feature flags:
```typescript
// From personal-account-navigation.config.tsx
featureFlagsConfig.enablePersonalAccountBilling
? {
label: 'common.routes.billing',
path: pathsConfig.app.personalAccountBilling,
Icon: <CreditCard className={iconClasses} />,
}
: undefined,
```
## Feature Flag Details
### Theme Toggle
Controls whether users can switch between light and dark themes. When disabled, the app uses `NEXT_PUBLIC_DEFAULT_THEME_MODE` exclusively.
### Account Deletion
Allows users to permanently delete their personal accounts. Disabled by default to prevent accidental data loss. Consider enabling for GDPR compliance.
### Team Accounts
Master switch for all team functionality. When disabled, the application operates in personal-account-only mode. Disabling this also hides team-related navigation and features.
### Team Creation
Controls whether users can create new teams. Set to `false` for enterprise scenarios where you provision teams manually.
### Team Deletion
Allows team owners to delete their teams. Disabled by default to prevent accidental data loss.
### Personal vs Team Billing
Choose one based on your business model:
- **Personal billing**: Each user subscribes individually (B2C)
- **Team billing**: Organizations subscribe and add team members (B2B)
Enabling both is possible but uncommon. Most SaaS applications use one model.
### Notifications
Enables the in-app notification system. When combined with `realtimeNotifications`, notifications appear instantly via Supabase Realtime.
### Language Priority
Controls language selection behavior:
- `application`: Use the app's default locale
- `user`: Respect the user's browser language preference
### Version Updater
When enabled, the app checks for updates and notifies users. Useful for deployed applications that receive frequent updates.
## Common Pitfalls
1. **Enabling both billing modes**: While technically possible, enabling both personal and team billing may create confusing user experience (unless your business model is a hybrid of both). Choose one model.
2. **Disabling teams after launch**: If you've collected team data and then disable teams, users lose access. Plan your model before launch.
3. **Forgetting deletion flows**: If you enable deletion, ensure you also handle cascading data deletion and GDPR compliance.
4. **Realtime without base notifications**: `realtimeNotifications` requires `enableNotifications` to be true. The realtime flag adds live updates, not the notification system itself.
## Related Topics
- [Application Configuration](/docs/next-supabase-turbo/configuration/application-configuration) - Core app settings
- [Environment Variables](/docs/next-supabase-turbo/configuration/environment-variables) - Complete variable reference
- [Navigation Configuration](/docs/next-supabase-turbo/configuration/personal-account-sidebar-configuration) - Sidebar customization

View File

@@ -0,0 +1,214 @@
---
status: "published"
label: "Paths Configuration"
title: "Paths Configuration in the Next.js Supabase SaaS Kit"
description: "Configure route paths for authentication, personal accounts, and team accounts in the Next.js Supabase SaaS Kit. Centralized path management for consistent navigation."
order: 3
---
The paths configuration at `apps/web/config/paths.config.ts` centralizes all route definitions. Instead of scattering magic strings throughout your codebase, reference paths from this single configuration file.
{% alert type="default" title="Why Centralize Paths" %}
Centralizing paths prevents typos, makes refactoring easier, and ensures consistency across navigation, redirects, and links throughout your application.
{% /alert %}
## Default Paths
### Authentication Paths
| Path Key | Default Value | Description |
|----------|---------------|-------------|
| `auth.signIn` | `/auth/sign-in` | Sign in page |
| `auth.signUp` | `/auth/sign-up` | Sign up page |
| `auth.verifyMfa` | `/auth/verify` | MFA verification |
| `auth.callback` | `/auth/callback` | OAuth callback handler |
| `auth.passwordReset` | `/auth/password-reset` | Password reset request |
| `auth.passwordUpdate` | `/update-password` | Password update completion |
### Personal Account Paths
| Path Key | Default Value | Description |
|----------|---------------|-------------|
| `app.home` | `/home` | Personal dashboard |
| `app.personalAccountSettings` | `/home/settings` | Profile settings |
| `app.personalAccountBilling` | `/home/billing` | Billing management |
| `app.personalAccountBillingReturn` | `/home/billing/return` | Billing portal return |
### Team Account Paths
| Path Key | Default Value | Description |
|----------|---------------|-------------|
| `app.accountHome` | `/home/[account]` | Team dashboard |
| `app.accountSettings` | `/home/[account]/settings` | Team settings |
| `app.accountBilling` | `/home/[account]/billing` | Team billing |
| `app.accountMembers` | `/home/[account]/members` | Team members |
| `app.accountBillingReturn` | `/home/[account]/billing/return` | Team billing return |
| `app.joinTeam` | `/join` | Team invitation acceptance |
## Configuration File
```typescript
import * as z from 'zod';
const PathsSchema = z.object({
auth: z.object({
signIn: z.string().min(1),
signUp: z.string().min(1),
verifyMfa: z.string().min(1),
callback: z.string().min(1),
passwordReset: z.string().min(1),
passwordUpdate: z.string().min(1),
}),
app: z.object({
home: z.string().min(1),
personalAccountSettings: z.string().min(1),
personalAccountBilling: z.string().min(1),
personalAccountBillingReturn: z.string().min(1),
accountHome: z.string().min(1),
accountSettings: z.string().min(1),
accountBilling: z.string().min(1),
accountMembers: z.string().min(1),
accountBillingReturn: z.string().min(1),
joinTeam: z.string().min(1),
}),
});
const pathsConfig = PathsSchema.parse({
auth: {
signIn: '/auth/sign-in',
signUp: '/auth/sign-up',
verifyMfa: '/auth/verify',
callback: '/auth/callback',
passwordReset: '/auth/password-reset',
passwordUpdate: '/update-password',
},
app: {
home: '/home',
personalAccountSettings: '/home/settings',
personalAccountBilling: '/home/billing',
personalAccountBillingReturn: '/home/billing/return',
accountHome: '/home/[account]',
accountSettings: `/home/[account]/settings`,
accountBilling: `/home/[account]/billing`,
accountMembers: `/home/[account]/members`,
accountBillingReturn: `/home/[account]/billing/return`,
joinTeam: '/join',
},
});
export default pathsConfig;
```
## Using Paths in Code
### In Server Components and Actions
```typescript
import pathsConfig from '~/config/paths.config';
import { redirect } from 'next/navigation';
// Redirect to sign in
redirect(pathsConfig.auth.signIn);
// Redirect to team dashboard
const teamSlug = 'acme-corp';
redirect(pathsConfig.app.accountHome.replace('[account]', teamSlug));
```
### In Client Components
```tsx
import pathsConfig from '~/config/paths.config';
import Link from 'next/link';
function Navigation() {
return (
<nav>
<Link href={pathsConfig.app.home}>Dashboard</Link>
<Link href={pathsConfig.app.personalAccountSettings}>Settings</Link>
</nav>
);
}
```
### Dynamic Team Paths
Team account paths contain `[account]` as a placeholder. Replace it with the actual team slug:
```typescript
import pathsConfig from '~/config/paths.config';
function getTeamPaths(teamSlug: string) {
return {
dashboard: pathsConfig.app.accountHome.replace('[account]', teamSlug),
settings: pathsConfig.app.accountSettings.replace('[account]', teamSlug),
billing: pathsConfig.app.accountBilling.replace('[account]', teamSlug),
members: pathsConfig.app.accountMembers.replace('[account]', teamSlug),
};
}
// Usage
const paths = getTeamPaths('acme-corp');
// paths.dashboard = '/home/acme-corp'
// paths.settings = '/home/acme-corp/settings'
```
## Adding Custom Paths
Extend the schema when adding new routes to your application:
```typescript
const PathsSchema = z.object({
auth: z.object({
// ... existing auth paths
}),
app: z.object({
// ... existing app paths
// Add your custom paths
projects: z.string().min(1),
projectDetail: z.string().min(1),
}),
});
const pathsConfig = PathsSchema.parse({
auth: {
// ... existing values
},
app: {
// ... existing values
projects: '/home/[account]/projects',
projectDetail: '/home/[account]/projects/[projectId]',
},
});
```
Then use the new paths:
```typescript
const projectsPath = pathsConfig.app.projects
.replace('[account]', teamSlug);
const projectDetailPath = pathsConfig.app.projectDetail
.replace('[account]', teamSlug)
.replace('[projectId]', projectId);
```
## Path Conventions
Follow these conventions when adding paths:
1. **Use lowercase with hyphens**: `/home/my-projects` not `/home/myProjects`
2. **Use brackets for dynamic segments**: `[account]`, `[projectId]`
3. **Keep paths shallow**: Avoid deeply nested routes when possible
4. **Group related paths**: Put team-related paths under `app.account*`
## Common Pitfalls
1. **Forgetting to replace dynamic segments**: Always replace `[account]` with the actual slug before using team paths in redirects or links.
2. **Hardcoding paths**: Don't use string literals like `'/home/settings'`. Always import from `pathsConfig` for consistency.
3. **Missing trailing slash consistency**: The kit doesn't use trailing slashes. Keep this consistent in your custom paths.
## Related Topics
- [Navigation Configuration](/docs/next-supabase-turbo/configuration/personal-account-sidebar-configuration) - Configure sidebar navigation
- [App Router Structure](/docs/next-supabase-turbo/installation/navigating-codebase) - Understand the route organization

View File

@@ -0,0 +1,291 @@
---
status: "published"
label: "Personal Account Navigation"
title: "Personal Account Navigation in Next.js Supabase"
description: "Configure the personal account sidebar navigation, layout style, and menu structure in the Next.js Supabase SaaS Kit."
order: 5
---
The personal account navigation at `apps/web/config/personal-account-navigation.config.tsx` defines the sidebar menu for personal workspaces. Add your own routes here to extend the dashboard navigation.
{% alert type="default" title="Where to Add Routes" %}
This is the file you'll edit most often when building your product. Add dashboard pages, settings sections, and feature-specific navigation items here.
{% /alert %}
## Layout Options
| Variable | Options | Default | Description |
|----------|---------|---------|-------------|
| `NEXT_PUBLIC_USER_NAVIGATION_STYLE` | `sidebar`, `header` | `sidebar` | Navigation layout style |
| `NEXT_PUBLIC_HOME_SIDEBAR_COLLAPSED` | `true`, `false` | `false` | Start with collapsed sidebar |
| `NEXT_PUBLIC_SIDEBAR_COLLAPSIBLE_STYLE` | `offcanvas`, `icon`, `none` | `icon` | How sidebar collapses |
### Sidebar Style (Default)
```bash
NEXT_PUBLIC_USER_NAVIGATION_STYLE=sidebar
```
Shows a vertical sidebar on the left with expandable sections.
### Header Style
```bash
NEXT_PUBLIC_USER_NAVIGATION_STYLE=header
```
Shows navigation in a horizontal header bar.
### Collapse Behavior
Control how the sidebar behaves when collapsed:
```bash
# Icon mode: Shows icons only when collapsed
NEXT_PUBLIC_SIDEBAR_COLLAPSIBLE_STYLE=icon
# Offcanvas mode: Slides in/out as an overlay
NEXT_PUBLIC_SIDEBAR_COLLAPSIBLE_STYLE=offcanvas
# None: Sidebar cannot be collapsed
NEXT_PUBLIC_SIDEBAR_COLLAPSIBLE_STYLE=none
```
## Default Configuration
The kit ships with these routes:
```tsx
import { CreditCard, Home, User } from 'lucide-react';
import * as z from 'zod';
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 routes = [
{
label: 'common.routes.application',
children: [
{
label: 'common.routes.home',
path: pathsConfig.app.home,
Icon: <Home className={iconClasses} />,
highlightMatch: `${pathsConfig.app.home}$`,
},
],
},
{
label: 'common.routes.settings',
children: [
{
label: 'common.routes.profile',
path: pathsConfig.app.personalAccountSettings,
Icon: <User className={iconClasses} />,
},
featureFlagsConfig.enablePersonalAccountBilling
? {
label: 'common.routes.billing',
path: pathsConfig.app.personalAccountBilling,
Icon: <CreditCard className={iconClasses} />,
}
: undefined,
].filter((route) => !!route),
},
] satisfies z.output<typeof NavigationConfigSchema>['routes'];
export const personalAccountNavigationConfig = NavigationConfigSchema.parse({
routes,
style: process.env.NEXT_PUBLIC_USER_NAVIGATION_STYLE,
sidebarCollapsed: process.env.NEXT_PUBLIC_HOME_SIDEBAR_COLLAPSED,
sidebarCollapsedStyle: process.env.NEXT_PUBLIC_SIDEBAR_COLLAPSIBLE_STYLE,
});
```
## Adding Custom Routes
### Simple Route
Add a route to an existing section:
```tsx
const routes = [
{
label: 'common.routes.application',
children: [
{
label: 'common.routes.home',
path: pathsConfig.app.home,
Icon: <Home className={iconClasses} />,
highlightMatch: `${pathsConfig.app.home}$`,
},
{
label: 'Projects',
path: '/home/projects',
Icon: <Folder className={iconClasses} />,
},
],
},
// ... rest of routes
];
```
### New Section
Add a new collapsible section:
```tsx
const routes = [
// ... existing sections
{
label: 'Your Store',
children: [
{
label: 'Products',
path: '/home/products',
Icon: <Package className={iconClasses} />,
},
{
label: 'Orders',
path: '/home/orders',
Icon: <ShoppingCart className={iconClasses} />,
},
{
label: 'Analytics',
path: '/home/analytics',
Icon: <BarChart className={iconClasses} />,
},
],
},
];
```
### Nested Routes
Create sub-menus within a section:
```tsx
const routes = [
{
label: 'Dashboard',
children: [
{
label: 'Overview',
path: '/home',
Icon: <LayoutDashboard className={iconClasses} />,
highlightMatch: '^/home$',
},
{
label: 'Projects',
path: '/home/projects',
Icon: <Folder className={iconClasses} />,
children: [
{
label: 'Active',
path: '/home/projects/active',
Icon: <Play className={iconClasses} />,
},
{
label: 'Archived',
path: '/home/projects/archived',
Icon: <Archive className={iconClasses} />,
},
],
},
],
},
];
```
### Conditional Routes
Show routes based on feature flags or user state:
```tsx
const routes = [
{
label: 'common.routes.settings',
children: [
{
label: 'common.routes.profile',
path: pathsConfig.app.personalAccountSettings,
Icon: <User className={iconClasses} />,
},
// Only show billing if enabled
featureFlagsConfig.enablePersonalAccountBilling
? {
label: 'common.routes.billing',
path: pathsConfig.app.personalAccountBilling,
Icon: <CreditCard className={iconClasses} />,
}
: undefined,
].filter((route) => !!route),
},
];
```
## Route Properties
| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `label` | `string` | Yes | Display text (supports i18n keys) |
| `path` | `string` | Yes | Route path |
| `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:
```tsx
{
label: 'common.routes.dashboard', // Uses translation
path: '/home',
Icon: <Home className={iconClasses} />,
}
```
Translation files are in `apps/web/i18n/messages/[locale]/common.json`:
```json
{
"routes": {
"dashboard": "Dashboard",
"settings": "Settings",
"profile": "Profile"
}
}
```
For quick prototyping, use plain strings:
```tsx
{
label: 'My Projects', // Plain string
path: '/home/projects',
Icon: <Folder className={iconClasses} />,
}
```
## Best Practices
1. **Use `pathsConfig`**: Import paths from the configuration instead of hardcoding strings.
2. **Group logically**: Put related routes in the same section.
3. **Use feature flags**: Conditionally show routes based on billing plans or user permissions.
4. **Consistent icons**: Use icons from `lucide-react` for visual consistency.
## Common Pitfalls
1. **Missing `highlightMatch` property**: The home route needs `highlightMatch` with a regex pattern to prevent it from matching all paths starting with `/home`.
2. **Forgetting to filter undefined**: When using conditional routes, always filter out `undefined` values with `.filter((route) => !!route)`.
3. **Wrong icon size**: Use `w-4` class for icons to match the navigation style.
## Related Topics
- [Team Account Navigation](/docs/next-supabase-turbo/configuration/team-account-sidebar-configuration) - Configure team navigation
- [Paths Configuration](/docs/next-supabase-turbo/configuration/paths-configuration) - Centralized path management
- [Adding Pages](/docs/next-supabase-turbo/development/marketing-pages) - Create new dashboard pages

View File

@@ -0,0 +1,265 @@
---
status: "published"
label: "Team Account Navigation"
title: "Team Account Navigation Configuration in the Next.js Supabase SaaS Kit"
description: "Configure the team account sidebar navigation, layout style, and menu structure in the Next.js Supabase SaaS Kit for B2B team workspaces."
order: 6
---
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.
{% alert type="default" title="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`).
{% /alert %}
## 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)
```bash
NEXT_PUBLIC_TEAM_NAVIGATION_STYLE=sidebar
```
### Header Style
```bash
NEXT_PUBLIC_TEAM_NAVIGATION_STYLE=header
```
## Default Configuration
The kit ships with these team routes:
```tsx
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} />,
highlightMatch: `${pathsConfig.app.accountHome.replace('[account]', account)}$`,
},
],
},
{
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:
```tsx
const getRoutes = (account: string) => [
{
label: 'common.routes.application',
children: [
{
label: 'common.routes.dashboard',
path: createPath(pathsConfig.app.accountHome, account),
Icon: <LayoutDashboard className={iconClasses} />,
highlightMatch: `${createPath(pathsConfig.app.accountHome, account)}$`,
},
{
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:
```tsx
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:
```tsx
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 |
| `highlightMatch` | `string` | No | Regex pattern for active route highlighting |
| `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:
```tsx
function createPath(path: string, account: string) {
return path.replace('[account]', account);
}
// Usage
const settingsPath = createPath('/home/[account]/settings', 'acme-corp');
// Result: '/home/acme-corp/settings'
```
For paths defined in `pathsConfig`, use the same pattern:
```tsx
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:
```tsx
{
label: 'common.routes.settings',
collapsible: false, // Always expanded
children: [
// ... routes
],
}
```
## Best Practices
1. **Always use `createPath`**: Never hardcode team slugs. Always use the helper function.
2. **Keep paths in `pathsConfig`**: When adding new routes, add them to `paths.config.ts` first.
3. **Filter undefined routes**: When using conditional routes, always add `.filter(Boolean)`.
4. **Use the `highlightMatch` property**: Add `highlightMatch` with a regex pattern to index routes to prevent matching nested paths.
5. **Consider mobile**: Test navigation on mobile devices. Complex nested menus can be hard to navigate.
## Common Pitfalls
1. **Forgetting to replace `[account]`**: If paths show `[account]` literally in the URL, you forgot to use `createPath`.
2. **Not exporting as function**: Team navigation must be a function that accepts the account slug, not a static object.
3. **Mixing personal and team routes**: Team routes should use `/home/[account]/...`, personal routes use `/home/...`.
4. **Hardcoding team slugs**: Never hardcode a specific team slug. Always use the `account` parameter.
## Related Topics
- [Personal Account Navigation](/docs/next-supabase-turbo/configuration/personal-account-sidebar-configuration) - Configure personal navigation
- [Paths Configuration](/docs/next-supabase-turbo/configuration/paths-configuration) - Centralized path management
- [Team Accounts](/docs/next-supabase-turbo/api/team-account-api) - Understanding team workspaces
- [Feature Flags](/docs/next-supabase-turbo/configuration/feature-flags-configuration) - Toggle team features