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:
committed by
GitHub
parent
4912e402a3
commit
7ebff31475
133
docs/configuration/application-configuration.mdoc
Normal file
133
docs/configuration/application-configuration.mdoc
Normal 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
|
||||
206
docs/configuration/authentication-configuration.mdoc
Normal file
206
docs/configuration/authentication-configuration.mdoc
Normal 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
|
||||
305
docs/configuration/environment-variables.mdoc
Normal file
305
docs/configuration/environment-variables.mdoc
Normal 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
|
||||
260
docs/configuration/feature-flags-configuration.mdoc
Normal file
260
docs/configuration/feature-flags-configuration.mdoc
Normal 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
|
||||
214
docs/configuration/paths-configuration.mdoc
Normal file
214
docs/configuration/paths-configuration.mdoc
Normal 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
|
||||
291
docs/configuration/personal-account-sidebar-configuration.mdoc
Normal file
291
docs/configuration/personal-account-sidebar-configuration.mdoc
Normal 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
|
||||
265
docs/configuration/team-account-sidebar-configuration.mdoc
Normal file
265
docs/configuration/team-account-sidebar-configuration.mdoc
Normal 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
|
||||
Reference in New Issue
Block a user