Project Overview
Introduction
The SaaS Boilerplate is an enterprise-grade Next.js 15 application designed for rapid SaaS product development. It combines modern web technologies with a WordPress-like extensibility model through themes, plugins, and config-driven entities. The architecture prioritizes performance, developer experience, and production-ready features out of the box.
Technology Stack
Core Framework & Runtime
- Next.js 15 - React framework with App Router, Server Components, and Turbopack
- React 19 - Latest React with Concurrent Features and Server Components
- TypeScript 5 - Strict type safety throughout the codebase
- Node.js - JavaScript runtime for server-side operations
- pnpm 10.17 - Fast, disk space efficient package manager with workspaces
Frontend Technologies
- Tailwind CSS v4 - Utility-first CSS framework for rapid UI development
- shadcn/ui - High-quality, accessible React component library
- TanStack Query v5.85 - Powerful data synchronization and server state management
- React Hook Form v7.62 - Performant, flexible form validation
- Zod v4.1 - TypeScript-first schema validation
- next-intl v4.3.4 - Internationalization with translation registry
Backend & Database
- PostgreSQL - Robust relational database (via Supabase)
- pg v8.16 - PostgreSQL client for Node.js
- Better Auth v1.3.5 - Modern authentication library with session management
- Row-Level Security (RLS) - Database-level data isolation per user
Authentication & Security
- Better Auth - Email/password + OAuth authentication
- Google OAuth - Social authentication integration
- API Key Authentication - Scope-based API access for external clients
- Dual Authentication System - Sessions for dashboard, API keys for external APIs
- Resend v5.0.0 - Transactional email service for auth emails
Testing & Quality
- Cypress v15.1 - End-to-end testing with Page Object Model (POM)
- Jest v30.1 - Unit testing framework with coverage reporting
- Playwright v1.55 - Browser automation and testing
- React Testing Library - Component testing utilities
- ESLint - Static code analysis and linting
- TypeScript Strict Mode - Maximum type safety
Build System
- Turbopack - Next.js 15's fast bundler (default in dev mode)
- pnpm Workspaces - Monorepo management
- Custom Build Scripts - Registry generation, theme compilation, docs building
Additional Libraries
- pdfjs-dist - PDF parsing and manipulation
- mammoth - Document format conversion
- clsx - Utility for constructing className strings
- date-fns - Modern JavaScript date utility library
Main Systems
The application is built around 8 core systems that work together to provide a complete SaaS platform:
1. Entity System
WordPress-like config-driven CRUD generation
The entity system allows developers to create complex data models through configuration files rather than writing boilerplate code. Each entity is defined in a *.config.ts file that specifies fields, permissions, UI settings, and behavior.
Key Features:
- Zero boilerplate CRUD operations
- Automatic API endpoint generation
- Auto-generated database schema
- Built-in permissions and validation
- Metadata support for custom fields
- Child/nested entity relationships
- Lifecycle hooks (beforeCreate, afterUpdate, etc.)
- Multi-tenancy with RLS
Example Entity:
// contents/themes/default/entities/tasks/tasks.config.ts
export const taskConfig: EntityConfig = {
slug: 'tasks',
names: { singular: 'Task', plural: 'Tasks' },
fields: [
{ name: 'title', type: 'string', required: true },
{ name: 'description', type: 'richtext' },
{ name: 'status', type: 'select', options: ['todo', 'in_progress', 'done'] },
{ name: 'dueDate', type: 'date' }
],
permissions: {
create: ['user', 'admin'],
read: ['user', 'admin'],
update: ['owner', 'admin'],
delete: ['owner', 'admin']
}
}
Location: core/lib/entities/
Learn More: Entity System Documentation
2. Registry System ⭐
Build-time static registries for ~17,255x performance improvement
The registry system is the performance foundation of the application. Instead of dynamically importing configurations at runtime (140ms), all entities, themes, plugins, and translations are discovered at build-time and compiled into static TypeScript registries (6ms).
Performance Impact:
- Entity loading: 6ms (build-time) vs 140ms (runtime) = ~17,255x faster
- Zero file system I/O at runtime
- Type-safe access to all dynamic content
- Instant availability without async operations
Key Registries:
- Entity Registry - All entity configurations
- Theme Registry - Active theme configuration and route handlers
- Plugin Registry - Plugin metadata and lifecycle hooks (server + client versions)
- Translation Registry - i18n message loaders per locale
- Route Handlers Registry - API route handlers for entities
- Config Registry - Entity-specific configurations
- Docs Registry - Documentation index
Build Process:
# Generated by scripts/build-registry.mjs
pnpm registry:build # One-time build
pnpm registry:build-watch # Watch mode during development
Usage Example:
// ❌ WRONG - Runtime I/O (slow)
const config = await import('@/contents/themes/default/entities/tasks/tasks.config')
// ✅ CORRECT - Build-time registry (fast)
import { ENTITY_REGISTRY } from '@/core/lib/registries/entity-registry'
const taskConfig = ENTITY_REGISTRY.tasks
Location: core/lib/registries/ (auto-generated - never edit manually)
Learn More: Registry System Documentation
3. Theme System
Pluggable themes with CSS compilation and asset management
The theme system allows complete UI customization without modifying core code. Themes can override components, define custom styles, add entities, and provide translations.
Key Features:
- CSS variable-based theming
- Dark/light mode support
- Component override patterns
- Asset management (auto-copy to public/)
- Theme-specific entities
- Theme-specific translations
- Build-time CSS compilation
Theme Structure:
contents/themes/[theme-name]/
├── theme.config.ts # Theme metadata and settings
├── app.config.ts # App configuration overrides
├── styles/ # Theme CSS (compiled to app/theme-styles.css)
├── public/ # Theme assets (copied to public/theme/)
│ ├── brand/ # Logos, brand assets
│ ├── images/ # Theme images
│ ├── fonts/ # Custom fonts
│ └── docs/ # Documentation images
├── entities/ # Theme-specific entities
├── messages/ # Theme translations
│ ├── en.json
│ └── es.json
├── components/ (optional) # Component overrides
└── middleware/ (optional) # Custom middleware
Build Process:
# Generated by scripts/build-theme.mjs
pnpm theme:build # Compile theme CSS and copy assets
Activation:
# .env.local
NEXT_PUBLIC_ACTIVE_THEME=default
Location: contents/themes/
Learn More: Theme System Documentation
4. Plugin System
WordPress-like plugin architecture for extensibility
The plugin system enables feature extensions without modifying core code. Plugins can add entities, API routes, UI components, and integrate with lifecycle hooks.
Key Features:
- Plugin lifecycle hooks (onLoad, onActivate, onUnload, onError)
- Plugin-specific entities
- Custom API routes
- UI component registration
- Environment-specific configuration
- Plugin dependencies
- Server-only and client-safe registries
Plugin Structure:
contents/plugins/[plugin-name]/
├── plugin.config.ts # Plugin metadata and lifecycle hooks
├── .env # Plugin environment variables
├── types/ # Plugin TypeScript types
├── lib/ # Plugin business logic
├── hooks/ # React hooks
├── components/ # Plugin UI components
├── entities/ (optional) # Plugin entities
├── migrations/ (optional) # Plugin database migrations
└── api/ (optional) # Plugin API routes
Example Plugins:
- AI Plugin - AI-powered features and completions
- Amplitude Plugin - Analytics and user tracking
- Billing Plugin - Subscription and payment processing
- Social Media Publisher - Multi-platform social posting
Registry Access:
// Server-side (full access)
import { PLUGIN_REGISTRY } from '@/core/lib/registries/plugin-registry'
// Client-side (safe subset)
import { PLUGIN_REGISTRY } from '@/core/lib/registries/plugin-registry.client'
Location: contents/plugins/
Learn More: Plugin System Documentation
5. Authentication System
Dual authentication with Better Auth + API keys
The authentication system provides comprehensive user management with session-based authentication for the dashboard and API key authentication for external clients.
Key Features:
- Email/password authentication with email verification
- Google OAuth integration (+ other providers)
- Session management with secure cookies
- Password reset flows
- API key generation with scopes
- Role-based access control (RBAC)
- Protected routes with middleware
- Dual authentication (sessions + API keys)
Authentication Methods:
-
Session Authentication (Dashboard)
- User login via email/password or OAuth
- Secure session cookies
- Automatic session refresh
- Used for web dashboard access
-
API Key Authentication (External APIs)
- Scope-based permissions
- Key rotation and revocation
- Used for external API clients
- Rate limiting per key
Middleware Protection:
// middleware.ts
export default async function middleware(request: NextRequest) {
const session = await auth.api.getSession({ headers: request.headers })
if (!session) {
return NextResponse.redirect(new URL('/login', request.url))
}
return NextResponse.next()
}
Dual Auth in API Routes:
// API routes support both auth methods
const authResult = await authenticateRequest(request)
if (authResult.type === 'api_key') {
// Check API key scopes
} else if (authResult.type === 'session') {
// Check user permissions
} else {
return createAuthError('Unauthorized', 401)
}
Location: core/lib/auth/
Learn More: Authentication Documentation
6. API v1 Architecture
RESTful API with dynamic endpoints and dual authentication
The API system automatically generates CRUD endpoints from entity configurations while supporting custom endpoint overrides and dual authentication.
API Route Structure:
/api/v1/[entity]/ # Dynamic entity endpoints
- GET (list all)
- POST (create)
/api/v1/[entity]/[id]/ # Single entity operations
- GET (read one)
- PATCH (update)
- DELETE (delete)
/api/v1/(contents)/[entity]/ # Custom endpoint overrides
Resolution Priority:
- Core Endpoints:
/api/v1/users,/api/v1/api-keys,/api/v1/auth - Custom Overrides:
/api/v1/(contents)/[entity]/(theme or plugin) - Dynamic Generation: Generated from entity registry
- 404: Entity not found in registry
Key Features:
- Automatic CRUD endpoint generation
- Query parameters (filtering, sorting, pagination, search)
- Dual authentication (sessions + API keys)
- Rate limiting per endpoint
- Metadata system integration
- Standardized error responses
- Response formatting and pagination
- Field selection and projection
Query Parameters Example:
# Filtering
GET /api/v1/tasks?status=in_progress&assignedTo=user123
# Sorting
GET /api/v1/tasks?sort=createdAt:desc
# Pagination
GET /api/v1/tasks?page=2&limit=20
# Field selection
GET /api/v1/tasks?fields=id,title,status
# Date range
GET /api/v1/tasks?createdAfter=2024-01-01&createdBefore=2024-12-31
Location: app/api/v1/, core/lib/api/
Learn More: API Documentation
7. Metadata System
Flexible metadata for all entities without schema migrations
The metadata system allows attaching custom key-value data to any entity without modifying database schemas. It supports user-specific metadata, merge strategies, and full audit trails.
Key Features:
- No schema migrations required
- User-specific metadata
- Entity-level metadata
- Global metadata
- Merge strategies (user > entity > global)
- Type-safe metadata access
- Full audit trail
- Query support
Metadata Merge Priority:
User Metadata (highest priority)
↓
Entity Metadata
↓
Global Metadata (lowest priority)
Use Cases:
- User preferences and settings
- Feature flags per user/entity
- Tenant-specific configuration
- Custom fields without migrations
- A/B testing configuration
- Third-party integration data
Example:
// Set metadata
await MetaService.createMeta(
entityId: 'task_123',
key: 'priority',
value: 'high',
userId: 'user_456'
)
// Get merged metadata
const metadata = await MetaService.getMergedMeta(
entityId: 'task_123',
userId: 'user_456'
)
// Returns: { priority: 'high', ...globalMeta, ...entityMeta }
Location: core/lib/services/meta.service.ts
Learn More: Entity Metadata Documentation
8. Internationalization (i18n)
Multi-locale support with translation registry
The i18n system powered by next-intl provides complete internationalization with translation sources from core, themes, and plugins merged at build-time.
Supported Locales:
- English (
en) - Spanish (
es) - Easily extensible to additional locales
Translation Sources (Merge Priority):
- Plugin Translations (highest priority)
- Theme Translations
- Core Translations (lowest priority)
Translation Structure:
core/messages/
├── en/
│ ├── common.json
│ ├── auth.json
│ └── ...
└── es/
├── common.json
├── auth.json
└── ...
contents/themes/[theme]/messages/
├── en.json
└── es.json
contents/plugins/[plugin]/messages/
├── en.json
└── es.json
Usage in Components:
// Server Component
import { getTranslations } from 'next-intl/server'
export default async function Page() {
const t = await getTranslations('common')
return <h1>{t('welcome')}</h1>
}
// Client Component
'use client'
import { useTranslations } from 'next-intl'
export function Component() {
const t = useTranslations('common')
return <h1>{t('welcome')}</h1>
}
Translation Registry:
// Generated by build-registry.mjs
import { TRANSLATION_REGISTRY } from '@/core/lib/registries/translation-registry'
// Lazy-loaded per locale
const messages = await TRANSLATION_REGISTRY['en']()
Location: core/messages/, core/lib/i18n/
Learn More: i18n Documentation
High-Level Architecture
Core vs Contents Separation
The application architecture is built on a clear separation between stable core code and pluggable content:
Core (core/)
- Stable, versioned application code
- UI components and layouts
- Services and business logic
- Type definitions
- Reusable utilities
- Testing infrastructure
Contents (contents/)
- Pluggable themes (multiple themes, one active)
- Plugin ecosystem (feature extensions)
- Dynamic entities (domain objects)
- Swappable without core changes
Benefits:
- Modularity: Clear separation of concerns
- Extensibility: Add features without modifying core
- Maintainability: Core updates don't break content
- Scalability: Multiple themes/plugins for different markets
- Testability: Test core and contents independently
Build-Time Generation Philosophy
Instead of loading configurations at runtime (slow, I/O intensive), the application generates static registries at build-time:
Traditional Approach (Slow):
// Runtime: ~140ms per entity
const config = await import(`./entities/${entityName}/config`)
const fields = await import(`./entities/${entityName}/fields`)
// Filesystem I/O, dynamic imports, async operations
Build-Time Approach (Fast):
// Build-time: Generate static registry
// Runtime: ~6ms per entity (from memory)
import { ENTITY_REGISTRY } from '@/core/lib/registries/entity-registry'
const config = ENTITY_REGISTRY[entityName]
// Instant access, no I/O, synchronous
Build Scripts:
scripts/build-registry.mjs- Generates all registriesscripts/build-theme.mjs- Compiles theme CSSscripts/build-docs-registry.mjs- Indexes documentation
Performance Impact:
- 17,255x faster entity access (140ms → 6ms)
- Zero filesystem I/O at runtime
- Lower memory footprint
- Faster application startup
- Better caching strategies
Zero Runtime I/O Approach
Philosophy: All configuration and content discovery happens at build-time, not runtime.
Allowed:
- ✅ UI code-splitting with
lazy(() => import('./Component')) - ✅ Dynamic imports for route-based code splitting (automatic in Next.js)
- ✅ Translation lazy-loading per locale (minimal, approved exception)
Prohibited:
- ❌
await import()for entity configurations - ❌
await import()for theme/plugin configurations - ❌ Direct imports from
@/contentsin app code - ❌ Runtime filesystem reads for configuration
- ❌
eval()or workarounds to hide dynamic imports
Enforcement:
# Pre-commit hook checks
pnpm check-dynamic-imports
# Validates:
# - No dynamic imports for content
# - No imports from @/contents (use registries)
# - No eval() usage for configuration
Learn More: Zero Dynamic Imports Policy
Protected Routes & Middleware
The application uses Next.js middleware for route protection and authentication:
Route Groups:
(public)/- Public pages (landing, login, signup, docs)(auth)/- Authentication pages (login, verify-email, reset-password)dashboard/- Protected dashboard (requires authentication)admin/- Admin panel (requires admin role)sector7/- Superadmin area (requires superadmin role)
Middleware Flow:
// middleware.ts
1. Check session with Better Auth
2. If no session and protected route → redirect to /login
3. If session but insufficient permissions → redirect to /403
4. If valid session and permissions → allow access
Route Protection Levels:
// core/lib/config/protected-paths.ts
export const PROTECTED_PATHS = {
public: ['/login', '/signup', '/docs'],
authenticated: ['/dashboard', '/profile', '/settings'],
admin: ['/admin'],
superadmin: ['/sector7']
}
Row-Level Security (RLS)
Database-level security ensures users can only access their own data:
RLS Cases:
-
Private (Case 1):
WHERE userId = current_user_id()- User can only access their own records
- Example: user tasks, user profile
-
Shared (Case 2):
WHERE true- All authenticated users can access
- Example: public entities, team resources
-
Public (Case 3):
WHERE public = true- Anonymous read access allowed
- Example: blog posts, documentation
-
Singleton (Case 4):
WHERE id = 'singleton'- System-level configuration
- Example: app settings, feature flags
Implementation:
-- Example RLS policy
CREATE POLICY "Users can view their own tasks"
ON tasks FOR SELECT
USING (user_id = current_user_id());
Query Helper:
import { queryWithRLS } from '@/core/lib/security/rls-helpers'
// Automatically applies RLS context
const tasks = await queryWithRLS<Task>(
'SELECT * FROM tasks WHERE status = $1',
['in_progress'],
userId // Sets app.user_id for RLS
)
Project Philosophy
Design Goals
-
Zero Tolerance for Errors
- No linting errors allowed
- No TypeScript errors allowed
- All tests must pass
- 90%+ coverage for critical paths
- 80%+ coverage for features
-
Performance First
- Core Web Vitals targets: LCP < 2.5s, FID < 100ms, CLS < 0.1
- Bundle size: < 100KB initial, < 500KB total
- Build-time generation for instant runtime access
- Code splitting and lazy loading
- Efficient caching strategies
-
Developer Experience
- Config-driven development (minimal boilerplate)
- Comprehensive type safety
- Automated testing with test-writer-fixer agent
- Clear documentation
- Claude Code agent integration
- Hot module replacement in development
-
Security By Default
- Dual authentication (sessions + API keys)
- Row-level security at database level
- CSRF protection
- Rate limiting
- Secure session management
- OAuth integration
-
Extensibility Without Modification
- Plugin system for features
- Theme system for UI
- Entity system for data models
- Never modify core for customization
Key Principles
1. Config Over Code
- Define entities through configuration
- Generate CRUD automatically
- Reduce boilerplate by 90%+
2. Build-Time Over Runtime
- Generate registries at build-time
- Compile theme CSS at build-time
- Index documentation at build-time
- Zero runtime I/O for configuration
3. Composition Over Inheritance
- Use React composition patterns
- Plugin system for features
- Theme system for UI
- Avoid deep inheritance hierarchies
4. Type Safety Everywhere
- Strict TypeScript mode
- No
anytypes - Comprehensive type definitions
- Runtime validation with Zod
5. Test Everything
- Unit tests for business logic
- E2E tests for user flows
- Integration tests for APIs
- Automated test generation
Performance Metrics
Registry System Performance
Entity Loading Time:
- Runtime I/O: ~140ms per entity
- Build-Time Registry: ~6ms per entity
- Improvement: ~17,255x faster (140ms → 6ms)
Theme Loading Time:
- Runtime I/O: ~120ms for theme config
- Build-Time Registry: <5ms for theme config
- Improvement: ~15,000x faster
Bundle Size:
- Initial Load: ~85KB (target: < 100KB)
- Total Bundle: ~420KB (target: < 500KB)
- First Contentful Paint (FCP): ~1.2s
- Largest Contentful Paint (LCP): ~2.1s (target: < 2.5s)
Database Performance
Query Performance (with RLS):
- Simple SELECT: ~5-10ms
- JOIN queries: ~15-30ms
- Complex aggregations: ~50-100ms
- Connection Pooling: 10-20 active connections
API Response Times:
- Entity List: ~50-100ms (with pagination)
- Entity Get: ~20-40ms (single record)
- Entity Create: ~30-60ms (with validation)
- Entity Update: ~30-60ms (with validation)
Frontend Performance
Core Web Vitals:
- LCP (Largest Contentful Paint): ~2.1s (target: < 2.5s)
- FID (First Input Delay): ~50ms (target: < 100ms)
- CLS (Cumulative Layout Shift): ~0.05 (target: < 0.1)
Build Times:
- Cold Build: ~45s
- Hot Build (Turbopack): ~200ms
- Registry Generation: ~2s
- Theme Compilation: ~500ms
Next Steps
Now that you understand the project architecture and main systems, explore specific areas:
- Core Library Organization - Understand the
core/lib/structure - Directory Structure - Complete project file organization
- Architecture Patterns - Design patterns used throughout
- TypeScript Standards - Type conventions and standards
- Development Workflow - .rules/ system and CI/CD
Last Updated: 2025-11-19 Version: 1.0.0 Status: Complete