Better SaaS Docs

Configuration

Better SaaS uses a centralized configuration system that manages application settings, feature flags, environment variables, and runtime configuration with type safety and validation.

Overview

The configuration system provides:

  • Environment Management: Development, staging, and production environments
  • Feature Flags: Dynamic feature toggling and A/B testing
  • Type Safety: TypeScript validation for all configuration
  • Runtime Configuration: Dynamic settings without rebuilding
  • Validation: Zod schemas for configuration validation
  • Centralized Management: Single source of truth for settings

Environment Variables

Environment Files Structure

.env.local          # Local development (not committed)
.env.example        # Template for environment variables
.env.production     # Production environment variables
.env.staging        # Staging environment variables

Core Environment Variables

# Application
NODE_ENV=development
NEXT_PUBLIC_APP_URL=http://localhost:3000
NEXT_PUBLIC_APP_NAME="Better SaaS"

# Database
DATABASE_URL=postgresql://user:password@localhost:5432/bettersaas

# Authentication
BETTER_AUTH_SECRET=your-super-secret-key-here
BETTER_AUTH_URL=http://localhost:3000
BETTER_AUTH_TRUSTED_ORIGINS=http://localhost:3000

# OAuth Providers
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret

# Payment (Stripe)
STRIPE_SECRET_KEY=sk_test_your-test-secret-key
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_your-test-publishable-key
STRIPE_WEBHOOK_SECRET=whsec_your-webhook-secret

# File Storage (Cloudflare R2)
CLOUDFLARE_R2_ACCESS_KEY_ID=your-access-key-id
CLOUDFLARE_R2_SECRET_ACCESS_KEY=your-secret-access-key
CLOUDFLARE_R2_BUCKET_NAME=your-bucket-name
CLOUDFLARE_R2_ENDPOINT=https://your-account-id.r2.cloudflarestorage.com
NEXT_PUBLIC_CLOUDFLARE_R2_PUBLIC_URL=https://your-domain.com

# Email (Resend)
RESEND_API_KEY=your-resend-api-key
RESEND_FROM_EMAIL=noreply@yourdomain.com

# Analytics (Optional)
NEXT_PUBLIC_GOOGLE_ANALYTICS_ID=GA_MEASUREMENT_ID
NEXT_PUBLIC_POSTHOG_KEY=your-posthog-key
NEXT_PUBLIC_POSTHOG_HOST=https://app.posthog.com

# Monitoring (Optional)
SENTRY_DSN=your-sentry-dsn
SENTRY_ORG=your-sentry-org
SENTRY_PROJECT=your-sentry-project

# Feature Flags
NEXT_PUBLIC_ENABLE_ANALYTICS=true
NEXT_PUBLIC_ENABLE_BLOG=true
NEXT_PUBLIC_ENABLE_DOCS=true
NEXT_PUBLIC_ENABLE_PAYMENTS=true

Environment Validation

// src/env.ts
import { z } from 'zod'

const envSchema = z.object({
  // Application
  NODE_ENV: z.enum(['development', 'staging', 'production']).default('development'),
  NEXT_PUBLIC_APP_URL: z.string().url(),
  NEXT_PUBLIC_APP_NAME: z.string().default('Better SaaS'),

  // Database
  DATABASE_URL: z.string().url(),

  // Authentication
  BETTER_AUTH_SECRET: z.string().min(32),
  BETTER_AUTH_URL: z.string().url(),
  BETTER_AUTH_TRUSTED_ORIGINS: z.string().optional(),

  // OAuth
  GITHUB_CLIENT_ID: z.string().optional(),
  GITHUB_CLIENT_SECRET: z.string().optional(),
  GOOGLE_CLIENT_ID: z.string().optional(),
  GOOGLE_CLIENT_SECRET: z.string().optional(),

  // Payment
  STRIPE_SECRET_KEY: z.string().optional(),
  NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: z.string().optional(),
  STRIPE_WEBHOOK_SECRET: z.string().optional(),

  // File Storage
  CLOUDFLARE_R2_ACCESS_KEY_ID: z.string().optional(),
  CLOUDFLARE_R2_SECRET_ACCESS_KEY: z.string().optional(),
  CLOUDFLARE_R2_BUCKET_NAME: z.string().optional(),
  CLOUDFLARE_R2_ENDPOINT: z.string().url().optional(),
  NEXT_PUBLIC_CLOUDFLARE_R2_PUBLIC_URL: z.string().url().optional(),

  // Email
  RESEND_API_KEY: z.string().optional(),
  RESEND_FROM_EMAIL: z.string().email().optional(),

  // Analytics
  NEXT_PUBLIC_GOOGLE_ANALYTICS_ID: z.string().optional(),
  NEXT_PUBLIC_POSTHOG_KEY: z.string().optional(),
  NEXT_PUBLIC_POSTHOG_HOST: z.string().url().optional(),

  // Monitoring
  SENTRY_DSN: z.string().url().optional(),
  SENTRY_ORG: z.string().optional(),
  SENTRY_PROJECT: z.string().optional(),

  // Feature Flags
  NEXT_PUBLIC_ENABLE_ANALYTICS: z.string().transform(val => val === 'true').default('false'),
  NEXT_PUBLIC_ENABLE_BLOG: z.string().transform(val => val === 'true').default('true'),
  NEXT_PUBLIC_ENABLE_DOCS: z.string().transform(val => val === 'true').default('true'),
  NEXT_PUBLIC_ENABLE_PAYMENTS: z.string().transform(val => val === 'true').default('true'),
})

export const env = envSchema.parse(process.env)

export type Env = z.infer<typeof envSchema>

Application Configuration

Core App Configuration

// src/config/app.config.ts
import { env } from '@/env'

export const APP_CONFIG = {
  name: env.NEXT_PUBLIC_APP_NAME,
  url: env.NEXT_PUBLIC_APP_URL,
  description: 'Modern SaaS application built with Next.js',
  
  // Application metadata
  meta: {
    title: 'Better SaaS - Modern SaaS Template',
    description: 'Build your SaaS application with Next.js, TypeScript, and modern tools',
    keywords: ['SaaS', 'Next.js', 'TypeScript', 'React', 'Tailwind CSS'],
    author: 'Better SaaS Team',
    version: '1.0.0',
  },

  // Social media and branding
  social: {
    twitter: '@bettersaas',
    github: 'https://github.com/yourusername/better-saas',
    discord: 'https://discord.gg/yourinvite',
  },

  // Contact information
  contact: {
    email: 'hello@bettersaas.com',
    support: 'support@bettersaas.com',
  },

  // Legal
  legal: {
    company: 'Better SaaS Inc.',
    address: '123 Main St, San Francisco, CA 94105',
    privacyPolicy: '/privacy',
    termsOfService: '/terms',
  },

  // Default settings
  defaults: {
    locale: 'en',
    theme: 'light',
    currency: 'USD',
    timezone: 'UTC',
  },
} as const

export type AppConfig = typeof APP_CONFIG

Feature Configuration

// src/config/features.config.ts
import { env } from '@/env'

export const FEATURES_CONFIG = {
  // Core features
  authentication: {
    enabled: true,
    providers: {
      email: true,
      github: !!env.GITHUB_CLIENT_ID,
      google: !!env.GOOGLE_CLIENT_ID,
    },
    features: {
      registration: true,
      emailVerification: false,
      twoFactorAuth: false,
      passwordReset: true,
    },
  },

  // Payment features
  payments: {
    enabled: env.NEXT_PUBLIC_ENABLE_PAYMENTS,
    provider: 'stripe',
    features: {
      subscriptions: true,
      oneTimePayments: true,
      invoices: true,
      refunds: true,
    },
    plans: {
      basic: {
        id: 'basic',
        name: 'Basic',
        price: 9.99,
        interval: 'month',
        features: ['5 projects', '10GB storage', 'Email support'],
      },
      pro: {
        id: 'pro',
        name: 'Pro',
        price: 29.99,
        interval: 'month',
        features: ['Unlimited projects', '100GB storage', 'Priority support'],
      },
    },
  },

  // File management
  fileManagement: {
    enabled: true,
    provider: 'r2',
    limits: {
      maxFileSize: 10 * 1024 * 1024, // 10MB
      maxFiles: 1000,
      allowedTypes: ['image/jpeg', 'image/png', 'application/pdf'],
    },
    features: {
      thumbnails: true,
      sharing: true,
      folders: true,
      search: true,
    },
  },

  // Analytics
  analytics: {
    enabled: env.NEXT_PUBLIC_ENABLE_ANALYTICS,
    providers: {
      googleAnalytics: !!env.NEXT_PUBLIC_GOOGLE_ANALYTICS_ID,
      posthog: !!env.NEXT_PUBLIC_POSTHOG_KEY,
    },
  },

  // Content features
  content: {
    blog: {
      enabled: env.NEXT_PUBLIC_ENABLE_BLOG,
      features: {
        comments: false,
        rss: true,
        search: true,
        categories: true,
      },
    },
    docs: {
      enabled: env.NEXT_PUBLIC_ENABLE_DOCS,
      features: {
        search: true,
        feedback: true,
        editOnGithub: true,
      },
    },
  },

  // Admin features
  admin: {
    enabled: true,
    features: {
      userManagement: true,
      analytics: true,
      systemSettings: true,
      auditLog: true,
    },
  },

  // Internationalization
  i18n: {
    enabled: true,
    defaultLocale: 'en',
    locales: ['en', 'zh'],
    features: {
      autoDetect: true,
      localizedRouting: true,
    },
  },
} as const

export type FeaturesConfig = typeof FEATURES_CONFIG

Theme Configuration

// src/config/theme.config.ts
export const THEME_CONFIG = {
  // Color schemes
  colors: {
    light: {
      primary: 'hsl(221, 83%, 53%)',
      secondary: 'hsl(210, 40%, 98%)',
      accent: 'hsl(210, 40%, 96%)',
      background: 'hsl(0, 0%, 100%)',
      foreground: 'hsl(222, 84%, 5%)',
      muted: 'hsl(210, 40%, 96%)',
      border: 'hsl(214, 32%, 91%)',
    },
    dark: {
      primary: 'hsl(221, 83%, 53%)',
      secondary: 'hsl(217, 33%, 17%)',
      accent: 'hsl(217, 33%, 17%)',
      background: 'hsl(222, 84%, 5%)',
      foreground: 'hsl(210, 40%, 98%)',
      muted: 'hsl(217, 33%, 17%)',
      border: 'hsl(217, 33%, 17%)',
    },
  },

  // Typography
  typography: {
    fontFamily: {
      sans: ['Inter', 'system-ui', 'sans-serif'],
      mono: ['Fira Code', 'monospace'],
    },
    fontSize: {
      xs: '0.75rem',
      sm: '0.875rem',
      base: '1rem',
      lg: '1.125rem',
      xl: '1.25rem',
      '2xl': '1.5rem',
      '3xl': '1.875rem',
      '4xl': '2.25rem',
    },
  },

  // Layout
  layout: {
    containerMaxWidth: '1200px',
    sidebarWidth: '240px',
    headerHeight: '64px',
    footerHeight: '80px',
  },

  // Animation
  animation: {
    duration: {
      fast: '150ms',
      normal: '300ms',
      slow: '500ms',
    },
    easing: {
      default: 'cubic-bezier(0.4, 0, 0.2, 1)',
      in: 'cubic-bezier(0.4, 0, 1, 1)',
      out: 'cubic-bezier(0, 0, 0.2, 1)',
    },
  },

  // Breakpoints
  breakpoints: {
    sm: '640px',
    md: '768px',
    lg: '1024px',
    xl: '1280px',
    '2xl': '1536px',
  },
} as const

export type ThemeConfig = typeof THEME_CONFIG
// src/config/navbar.config.ts
import { FEATURES_CONFIG } from './features.config'

export const NAVBAR_CONFIG = {
  // Main navigation
  main: [
    {
      title: 'Home',
      href: '/',
      icon: 'home',
    },
    {
      title: 'Features',
      href: '/features',
      icon: 'features',
    },
    {
      title: 'Pricing',
      href: '/pricing',
      icon: 'pricing',
    },
    ...(FEATURES_CONFIG.content.blog.enabled ? [{
      title: 'Blog',
      href: '/blog',
      icon: 'blog',
    }] : []),
    ...(FEATURES_CONFIG.content.docs.enabled ? [{
      title: 'Docs',
      href: '/docs',
      icon: 'docs',
    }] : []),
  ],

  // Dashboard navigation
  dashboard: [
    {
      title: 'Overview',
      href: '/dashboard',
      icon: 'dashboard',
    },
    {
      title: 'Projects',
      href: '/dashboard/projects',
      icon: 'projects',
    },
    ...(FEATURES_CONFIG.fileManagement.enabled ? [{
      title: 'Files',
      href: '/dashboard/files',
      icon: 'files',
    }] : []),
    ...(FEATURES_CONFIG.payments.enabled ? [{
      title: 'Billing',
      href: '/dashboard/billing',
      icon: 'billing',
    }] : []),
    {
      title: 'Settings',
      href: '/dashboard/settings',
      icon: 'settings',
    },
  ],

  // Admin navigation
  admin: [
    {
      title: 'Users',
      href: '/admin/users',
      icon: 'users',
      permission: 'manage:users',
    },
    {
      title: 'Analytics',
      href: '/admin/analytics',
      icon: 'analytics',
      permission: 'view:analytics',
    },
    {
      title: 'Settings',
      href: '/admin/settings',
      icon: 'settings',
      permission: 'manage:settings',
    },
  ],

  // Footer navigation
  footer: {
    product: [
      { title: 'Features', href: '/features' },
      { title: 'Pricing', href: '/pricing' },
      { title: 'Changelog', href: '/changelog' },
    ],
    company: [
      { title: 'About', href: '/about' },
      { title: 'Contact', href: '/contact' },
      { title: 'Careers', href: '/careers' },
    ],
    legal: [
      { title: 'Privacy', href: '/privacy' },
      { title: 'Terms', href: '/terms' },
      { title: 'Security', href: '/security' },
    ],
  },
} as const

export type NavbarConfig = typeof NAVBAR_CONFIG

Payment Configuration

// src/config/payment.config.ts
import { env } from '@/env'

export const PAYMENT_CONFIG = {
  // Provider configuration
  provider: 'stripe',
  enabled: env.NEXT_PUBLIC_ENABLE_PAYMENTS,
  
  // Currency settings
  currency: 'USD',
  locale: 'en-US',

  // Subscription plans
  plans: {
    basic: {
      id: 'basic',
      name: 'Basic',
      description: 'Perfect for individuals and small teams',
      price: 9.99,
      interval: 'month',
      stripePriceId: 'price_basic_monthly',
      features: [
        'Up to 5 projects',
        '10GB storage',
        'Email support',
        'Basic analytics',
      ],
      limits: {
        projects: 5,
        storage: 10 * 1024 * 1024 * 1024, // 10GB
        users: 1,
        apiCalls: 1000,
      },
    },
    pro: {
      id: 'pro',
      name: 'Pro',
      description: 'For growing teams and businesses',
      price: 29.99,
      interval: 'month',
      stripePriceId: 'price_pro_monthly',
      features: [
        'Unlimited projects',
        '100GB storage',
        'Priority support',
        'Advanced analytics',
        'Team collaboration',
      ],
      limits: {
        projects: -1, // Unlimited
        storage: 100 * 1024 * 1024 * 1024, // 100GB
        users: 10,
        apiCalls: 10000,
      },
    },
    enterprise: {
      id: 'enterprise',
      name: 'Enterprise',
      description: 'For large organizations with advanced needs',
      price: 99.99,
      interval: 'month',
      stripePriceId: 'price_enterprise_monthly',
      features: [
        'Everything in Pro',
        '1TB storage',
        'Dedicated support',
        'Custom integrations',
        'SLA guarantee',
      ],
      limits: {
        projects: -1, // Unlimited
        storage: 1024 * 1024 * 1024 * 1024, // 1TB
        users: -1, // Unlimited
        apiCalls: -1, // Unlimited
      },
    },
  },

  // Billing settings
  billing: {
    trialPeriod: 14, // days
    gracePeriod: 3, // days
    invoicePrefix: 'INV-',
    taxRate: 0.1, // 10%
  },

  // Feature access by plan
  features: {
    basic: [
      'projects',
      'storage',
      'support',
      'analytics',
    ],
    pro: [
      'projects',
      'storage',
      'support',
      'analytics',
      'collaboration',
      'integrations',
    ],
    enterprise: [
      'projects',
      'storage',
      'support',
      'analytics',
      'collaboration',
      'integrations',
      'sla',
      'custom',
    ],
  },
} as const

export type PaymentConfig = typeof PAYMENT_CONFIG
export type PlanId = keyof typeof PAYMENT_CONFIG.plans
export type Plan = typeof PAYMENT_CONFIG.plans[PlanId]

Configuration Validation

Runtime Configuration Validation

// src/lib/config-validation.ts
import { z } from 'zod'

// Configuration schemas
const appConfigSchema = z.object({
  name: z.string().min(1),
  url: z.string().url(),
  description: z.string().min(1),
  meta: z.object({
    title: z.string().min(1),
    description: z.string().min(1),
    keywords: z.array(z.string()),
    author: z.string().min(1),
    version: z.string().min(1),
  }),
})

const featuresConfigSchema = z.object({
  authentication: z.object({
    enabled: z.boolean(),
    providers: z.object({
      email: z.boolean(),
      github: z.boolean(),
      google: z.boolean(),
    }),
  }),
  payments: z.object({
    enabled: z.boolean(),
    provider: z.enum(['stripe', 'paddle']),
  }),
  fileManagement: z.object({
    enabled: z.boolean(),
    provider: z.enum(['r2', 's3']),
    limits: z.object({
      maxFileSize: z.number().positive(),
      maxFiles: z.number().positive(),
      allowedTypes: z.array(z.string()),
    }),
  }),
})

// Validation functions
export function validateAppConfig(config: unknown) {
  return appConfigSchema.parse(config)
}

export function validateFeaturesConfig(config: unknown) {
  return featuresConfigSchema.parse(config)
}

// Configuration loader with validation
export function loadConfig<T>(
  configLoader: () => T,
  validator: (config: unknown) => T
): T {
  try {
    const config = configLoader()
    return validator(config)
  } catch (error) {
    console.error('Configuration validation failed:', error)
    throw new Error('Invalid configuration')
  }
}

Configuration Hooks

useConfig Hook

// src/hooks/use-config.ts
import { useContext, createContext, ReactNode } from 'react'
import { APP_CONFIG, AppConfig } from '@/config/app.config'
import { FEATURES_CONFIG, FeaturesConfig } from '@/config/features.config'
import { THEME_CONFIG, ThemeConfig } from '@/config/theme.config'

interface ConfigContextType {
  app: AppConfig
  features: FeaturesConfig
  theme: ThemeConfig
}

const ConfigContext = createContext<ConfigContextType | undefined>(undefined)

export function ConfigProvider({ children }: { children: ReactNode }) {
  const config = {
    app: APP_CONFIG,
    features: FEATURES_CONFIG,
    theme: THEME_CONFIG,
  }

  return (
    <ConfigContext.Provider value={config}>
      {children}
    </ConfigContext.Provider>
  )
}

export function useConfig() {
  const context = useContext(ConfigContext)
  if (!context) {
    throw new Error('useConfig must be used within a ConfigProvider')
  }
  return context
}

// Specific config hooks
export function useAppConfig() {
  const { app } = useConfig()
  return app
}

export function useFeaturesConfig() {
  const { features } = useConfig()
  return features
}

export function useThemeConfig() {
  const { theme } = useConfig()
  return theme
}

Feature Flags

Feature Flag System

// src/lib/feature-flags.ts
import { FEATURES_CONFIG } from '@/config/features.config'

export type FeatureFlag = keyof typeof FEATURES_CONFIG

export function isFeatureEnabled(feature: FeatureFlag): boolean {
  const config = FEATURES_CONFIG[feature]
  return typeof config === 'object' && 'enabled' in config 
    ? config.enabled 
    : Boolean(config)
}

export function getFeatureConfig<T extends FeatureFlag>(
  feature: T
): typeof FEATURES_CONFIG[T] {
  return FEATURES_CONFIG[feature]
}

// Feature flag component
export function FeatureFlag({ 
  feature, 
  children, 
  fallback = null 
}: {
  feature: FeatureFlag
  children: React.ReactNode
  fallback?: React.ReactNode
}) {
  if (!isFeatureEnabled(feature)) {
    return <>{fallback}</>
  }
  
  return <>{children}</>
}

// Feature flag hook
export function useFeatureFlag(feature: FeatureFlag) {
  return isFeatureEnabled(feature)
}

Configuration Management

Dynamic Configuration

// src/lib/dynamic-config.ts
import { useState, useEffect } from 'react'

interface DynamicConfig {
  maintenance: boolean
  announcements: string[]
  experimentalFeatures: Record<string, boolean>
}

export function useDynamicConfig() {
  const [config, setConfig] = useState<DynamicConfig>({
    maintenance: false,
    announcements: [],
    experimentalFeatures: {},
  })

  useEffect(() => {
    // Load dynamic configuration from API
    fetch('/api/config')
      .then(res => res.json())
      .then(setConfig)
      .catch(console.error)
  }, [])

  return config
}

// Server-side dynamic config
export async function getDynamicConfig(): Promise<DynamicConfig> {
  // This could fetch from a database, external API, or config service
  return {
    maintenance: process.env.MAINTENANCE_MODE === 'true',
    announcements: process.env.ANNOUNCEMENTS?.split(',') || [],
    experimentalFeatures: {
      newDashboard: process.env.EXPERIMENTAL_NEW_DASHBOARD === 'true',
      betaFeatures: process.env.EXPERIMENTAL_BETA_FEATURES === 'true',
    },
  }
}

Configuration API

// src/app/api/config/route.ts
import { NextResponse } from 'next/server'
import { getDynamicConfig } from '@/lib/dynamic-config'

export async function GET() {
  try {
    const config = await getDynamicConfig()
    return NextResponse.json(config)
  } catch (error) {
    return NextResponse.json(
      { error: 'Failed to load configuration' },
      { status: 500 }
    )
  }
}

Environment-Specific Configuration

Development Configuration

// src/config/development.config.ts
export const DEVELOPMENT_CONFIG = {
  debug: true,
  logging: {
    level: 'debug',
    console: true,
    file: false,
  },
  database: {
    logging: true,
    ssl: false,
  },
  cache: {
    enabled: false,
  },
  rateLimit: {
    enabled: false,
  },
} as const

Production Configuration

// src/config/production.config.ts
export const PRODUCTION_CONFIG = {
  debug: false,
  logging: {
    level: 'error',
    console: false,
    file: true,
  },
  database: {
    logging: false,
    ssl: true,
  },
  cache: {
    enabled: true,
    ttl: 3600,
  },
  rateLimit: {
    enabled: true,
    windowMs: 15 * 60 * 1000, // 15 minutes
    max: 100,
  },
} as const

Testing Configuration

Test Configuration

// src/config/test.config.ts
export const TEST_CONFIG = {
  database: {
    url: 'postgresql://test:test@localhost:5432/bettersaas_test',
    logging: false,
  },
  auth: {
    secret: 'test-secret-key',
    skipVerification: true,
  },
  storage: {
    provider: 'memory',
  },
  email: {
    provider: 'mock',
  },
} as const

Best Practices

Configuration Organization

  1. Separate Concerns: Keep different types of configuration in separate files
  2. Type Safety: Use TypeScript for all configuration
  3. Validation: Validate configuration at startup
  4. Environment Variables: Use environment variables for sensitive data
  5. Defaults: Provide sensible defaults for all configuration

Security Considerations

  1. Secret Management: Never commit secrets to version control
  2. Environment Separation: Use different configurations for different environments
  3. Validation: Validate all configuration inputs
  4. Access Control: Limit access to configuration endpoints

Performance Optimization

  1. Lazy Loading: Load configuration only when needed
  2. Caching: Cache configuration to avoid repeated reads
  3. Bundling: Optimize configuration for client-side bundling

Troubleshooting

Common Issues

  1. Missing Environment Variables: Check .env.example for required variables
  2. Invalid Configuration: Use validation schemas to catch errors early
  3. Type Errors: Ensure all configuration is properly typed
  4. Runtime Errors: Validate configuration at application startup

Debug Tools

// src/lib/config-debug.ts
export function debugConfiguration() {
  if (process.env.NODE_ENV === 'development') {
    console.log('App Config:', APP_CONFIG)
    console.log('Features Config:', FEATURES_CONFIG)
    console.log('Environment Variables:', process.env)
  }
}