This documentation provides a comprehensive and in-depth technical reference for developers using the UllrAI SaaS Starter Kit. Whether you want to quickly launch a new project or perform deep customization and secondary development, this documentation will provide you with the necessary guidance.
This documentation provides a comprehensive and in-depth technical reference for developers using the UllrAI SaaS Starter Kit. Whether you want to quickly launch a new project or perform deep customization and secondary development, this documentation will provide you with the necessary guidance.
1. Project Overview
1.1. Project Introduction
UllrAI SaaS Starter Kit is a free, open-source, production-ready full-stack SaaS starter kit. It integrates the most respected technologies and practices in modern web development, designed to help developers launch their next SaaS project at unprecedented speed, allowing you to focus on business logic rather than infrastructure setup.
Core Features: Provides authentication, payment subscriptions, database management, file uploads, content management, and other core SaaS application features.
Technology Stack: Based on Next.js 15 App Router, TypeScript, PostgreSQL, Drizzle ORM, and integrates Creem payments, Resend email service, and Cloudflare R2 file storage.
Use Cases:
Quickly build full-stack SaaS applications requiring user login and paid subscription features.
As a practical project for learning modern full-stack web development technologies.
Provide a stable, scalable initial scaffold for enterprise-level projects.
Independent developers or small teams quickly validate business ideas.
1.2. Quick Start
1.Clone the project
git clone https://github.com/ullrai/saas-starter.git
cd saas-starter
2.Install dependencies
pnpm install
3.Configure environment Copy .env.example to .env and fill in all required environment variables.
cp .env.example .env
4.Sync database Make sure your local PostgreSQL database is running, then execute:
pnpm db:push
5.Run development server
pnpm dev
The application will run at http://localhost:3000.
1.3. Feature List
Modern Framework: Next.js 15 (App Router, RSC), React 19, TypeScript
graph TD
subgraph "Frontend (Browser)"
A[User] --> B{Next.js App};
end
subgraph "Vercel Platform"
B -- React Server Components --> C["UI (shadcn/ui, Tailwind)"];
B -- API Routes/Server Actions --> D[Backend Logic];
end
subgraph "Core Services"
D -- ORM --> E[Drizzle ORM];
E --> F[(PostgreSQL)];
D -- Auth API --> G[Better-Auth];
D -- Payment API --> H[Creem];
D -- Email API --> I[Resend];
D -- Storage API --> J[Cloudflare R2];
end
subgraph "Content Management (CMS)"
K[Keystatic] -- Reads/Writes --> L[Markdown/JSON in Git];
B -- Reads data --> L;
end
G -- OAuth --> M[Google/GitHub/LinkedIn];
H -- Webhooks --> D;
style A fill:#f9f,stroke:#333,stroke-width:2px;
style F fill:#add,stroke:#333,stroke-width:2px;
style J fill:#f90,stroke:#333,stroke-width:2px;
style H fill:#f66,stroke:#333,stroke-width:2px;
style I fill:#9cf,stroke:#333,stroke-width:2px;
src/app/layout.tsx: The project's root layout that wraps all pages. It handles:
Setting HTML lang attribute and font variables.
Integrating ThemeProvider for dark/light mode.
Integrating NextTopLoader for page loading progress.
Integrating Toaster for global notifications.
Integrating CookieConsent for cookie consent management.
middleware.ts: Runs before requests reach pages, core for route protection.
Checks user session cookies.
Redirects to /login if user is not logged in but accessing /dashboard/*.
Redirects to /dashboard if user is logged in but accessing /login or /signup.
src/app/dashboard/layout.tsx: Root layout for the dashboard.
Uses SessionGuard component to protect all child routes. SessionGuard is a client component that verifies session existence, redirects to login if not found, and shows loading animation during verification.
Renders AppSidebar and main content area SidebarInset.
2.2.2. Configuration System Design
The project's configuration is highly centralized for easy maintenance and extension.
Environment Variables (env.ts): Uses @t3-oss/env-nextjs to enforce environment variable validation. All environment variables (like API keys, database URLs) must be defined in the .env file and accessed through env.ts for type safety. This prevents runtime errors due to missing environment variables.
Application Constants (src/lib/config/constants.ts): Stores app name, description, contact email, and other hardcoded values that don't change frequently.
Product Plans (src/lib/config/products.ts): Centrally defines all paid plans. Each plan includes internal ID, name, feature list, and product IDs in different payment providers (like Creem). This structure makes it easy to add new plans or switch payment providers.
User Roles (src/lib/config/roles.ts): Defines user roles and their hierarchical relationships (user, admin, super_admin). Helper functions like hasRole provide unified permission checking logic.
File Upload (src/lib/config/upload.ts): Centrally manages all file upload rules, including maximum file size, allowed file types, etc. All upload paths (client and server-side) share this configuration, ensuring rule consistency.
2.2.3. Routing Architecture
The project uses Next.js App Router and leverages Route Groups for logical page separation.
(pages): Contains all public pages like home, about, blog, pricing, etc. Uses src/app/(pages)/layout.tsx to provide unified header and footer.
(auth): Contains authentication flow pages like login, signup. Uses src/app/(auth)/layout.tsx to provide a centered, clean layout.
(dashboard): Contains all pages requiring user login. Its layout src/app/dashboard/layout.tsx implements route protection through SessionGuard.
2.2.4. Build and Packaging Process
next.config.ts: Next.js core configuration file.
Configures images.remotePatterns to allow loading images from Unsplash and Cloudflare R2.
Integrates @next/bundle-analyzer. When ANALYZE environment variable is set to true, running pnpm analyze generates and opens bundle size analysis report after build, helping developers optimize frontend resource size.
package.json:
dev: Starts development server with Next.js 15's --turbo mode for faster local compilation.
build: Builds production application.
start: Starts production server.
3. Development Guide
3.1. Environment Setup
Install Tools:
Node.js v20.x or higher.
pnpm (npm install -g pnpm).
PostgreSQL database (recommended using Docker: docker run --name my-postgres -e POSTGRES_PASSWORD=mysecretpassword -p 5432:5432 -d postgres).
Clone and Install:
git clone https://github.com/ullrai/saas-starter.git
cd saas-starter
pnpm install
Configure Environment Variables:
Copy .env.example to .env.
Generate a secure BETTER_AUTH_SECRET: openssl rand -base64 32.
Fill in your PostgreSQL DATABASE_URL.
Register and obtain API keys for Creem, Resend, Cloudflare R2, and fill them in the .env file.
Database Setup:
Development: pnpm db:push synchronizes changes from database/schema.ts directly to the database, suitable for rapid iteration.
Production: Must use migration files. Process:
pnpm db:generate:prod: Generate production migration SQL files.
Deploy code and migration files to production.
Run pnpm db:migrate:prod in production to apply migrations.
3.2. Development Workflow
Start Development Server: pnpm dev
Modify Database:
Edit database/schema.ts.
Run pnpm db:push to sync changes.
Create New Pages:
Create new folders and page.tsx files in app/(pages) or app/(dashboard).
Create API Routes:
Create new folders and route.ts files in the app/api directory.
Create Server Actions:
Create new files in the lib/actions directory using the "use server"; directive.
Code Checking:
Run pnpm lint to check code style.
Run pnpm prettier:format to format code.
3.3. Code Standards
Naming Conventions:
Components use PascalCase, e.g., FileUploader.
Functions and variables use camelCase.
Constants use UPPER_SNAKE_CASE.
File Organization:
Page components are placed in their respective app route folders, usually in _components subdirectories.
Reusable components are placed in the components directory.
Logic, types, configurations are separated into lib, types, schemas directories.
Comment Requirements:
Use JSDoc comments for complex functions or logic blocks.
Use inline comments for non-intuitive code.
4. Feature Module Details
4.1. Authentication System (Better-Auth)
This starter kit uses the better-auth library to provide a complete authentication solution.
Core Configuration: src/lib/auth/server.ts
Configures Drizzle database adapter.
Dynamically loads social login providers (Google, GitHub, LinkedIn), only enabled when corresponding CLIENT_ID and SECRET are provided in .env.
Integrates magicLink plugin and configures using Resend for email sending.
API Route: app/api/auth/[...all]/route.ts
This is a dynamic route that captures all better-auth authentication requests (like /api/auth/magic-link, /api/auth/google/login, etc.) and hands them to auth.handler.
Client: src/lib/auth/client.ts
Provides methods for interacting with the authentication system in client components, like signIn, signOut, useSession, etc.
Authentication Flow (Magic Link):
sequenceDiagram
participant User
participant Client as Frontend (AuthForm)
participant Server as Server (API)
participant Resend as Email Service
User->>Client: Enter email and click login
Client->>Server: POST /api/auth/magic-link
Server->>Server: Generate time-limited Token
Server->>Resend: Request to send email (with Token URL)
Resend-->>User: Send magic link email
User->>User: Click link in email
Client->>Server: GET /api/auth/callback?token=...
Server->>Server: Verify Token, create session
Server-->>Client: Set session Cookie and redirect to /dashboard
4.2. Database & ORM (Drizzle)
Schema Definition: database/schema.ts is the single source of truth for all database tables, defining table structures, relationships, and constraints using Drizzle ORM syntax.
Client Initialization: database/index.ts initializes the Drizzle client and applies different connection pool configurations based on environment (Serverless or traditional server) (src/lib/database/connection.ts).
Migration Management:
The project maintains two separate migration histories for development and production environments, located in database/migrations/development and database/migrations/production respectively.
pnpm db:generate & pnpm db:generate:prod: Generate SQL migration files based on changes in schema.ts.
pnpm db:push: Development only, directly syncs schema to database, loses history.
pnpm db:migrate:dev & pnpm db:migrate:prod: Apply migration files to database.
4.3. Payment & Subscriptions (Creem)
Abstraction Layer: src/lib/billing/index.ts exports a unified billing object, making it easy to switch to other payment providers (like Stripe) in the future without modifying upper-level business code.
Provider Implementation: src/lib/billing/creem/provider.ts is the specific implementation for Creem payment provider, encapsulating logic for creating checkout sessions, customer portals, and handling webhooks.
API Interfaces:
/api/billing/checkout: Creates payment sessions. Returns 409 Conflict status and management link when user tries to purchase existing subscription.
/api/billing/portal: Creates a URL to Creem customer portal where users can manage their subscriptions.
/api/billing/webhooks/creem: Receives webhook events from Creem for updating subscription status, recording payments, etc.
This is the default method used by the FileUploader component, offering better performance.
Flow Diagram:
sequenceDiagram
participant User
participant FileUploader as Frontend Component
participant Server as Server API
participant R2 as Cloudflare R2
User->>FileUploader: Select/drag files
FileUploader->>FileUploader: Client-side validation (type/size), image compression
FileUploader->>Server: POST /api/upload/presigned-url (request upload URL)
Server->>Server: Verify identity and file metadata
Server->>R2: Request presigned URL
R2-->>Server: Return presigned URL
Server-->>FileUploader: Return presigned URL
FileUploader->>R2: PUT (direct file upload)
R2-->>FileUploader: Upload success
FileUploader->>FileUploader: onUploadComplete callback
4.4.2. Server-side Proxy Upload
This mode allows server-side processing before storage.
Flow Diagram:
sequenceDiagram
participant Client as Client/Script
participant Server as Server API
participant R2 as Cloudflare R2
Client->>Server: POST /api/upload/server-upload (multipart/form-data)
Server->>Server: Verify identity and file
Server->>R2: Stream file
R2-->>Server: Upload success
Server->>Server: Record to database
Server-->>Client: Return upload result
4.5. Blog & Content Management (Keystatic)
CMS: Uses Keystatic as Git-based CMS, all content stored in Markdown and JSON files under content/ directory.
Admin Interface: In development environment, access /keystatic to enter admin dashboard. For security, this interface is disabled by default in production.
Content Reading:
@keystatic/core/reader used for safely reading content under content/ directory on server-side.
src/app/(pages)/blog/page.tsx: Blog list page, reads all articles.
src/app/(pages)/blog/[slug]/page.tsx: Blog detail page, reads single article and uses @markdoc/markdoc to parse Markdoc content.
4.6. Admin Dashboard
Provides a powerful, extensible data management system.
Generic Table Manager: components/admin/generic-table/generic-table-manager.tsx is a core component that can automatically generate a complete CRUD management interface for any table declared in src/lib/config/admin-tables.ts.
Configuration-Driven:
Add Drizzle table object in src/lib/config/admin-tables.ts.
Add navigation link in genericTableNavigation in src/app/dashboard/_components/app-sidebar.tsx.
Access new management page at /dashboard/admin/tables/[tableName].
Server Actions: All CRUD operations completed through type-safe Server Actions in src/lib/actions/admin-generic.ts, no need to write additional API routes.
Schema Parsing: src/lib/admin/schema-generator.ts dynamically parses Drizzle schema, automatically generates forms and validation rules (Zod), greatly simplifying backend development.
5. Secondary Development Guide
5.1. Extension Point Identification
Add New Pages: Create new routes in app/(pages) or app/(dashboard).
Add New Admin Management Tables:
Define new table in database/schema.ts.
Register the table in enabledTablesMap in src/lib/config/admin-tables.ts.
Add navigation link in src/app/dashboard/_components/app-sidebar.tsx.
Add New Payment Provider:
Create new provider implementation file under src/lib/billing/, must follow PaymentProvider interface in src/lib/billing/provider.ts.
Modify PAYMENT_PROVIDER logic in src/lib/billing/index.ts to switch to new provider.
Customize Email Templates: Create or modify React Email components in src/emails/ directory.
Customize UI Components: Modify shadcn/ui components or add new ones in src/components/ui/.
5.2. API Reference
Route
Method
Description
/api/auth/[...all]
GET, POST
Handle all better-auth authentication requests.
/api/billing/checkout
POST
Create payment session.
/api/billing/portal
GET
Get customer portal URL.
/api/billing/webhooks/creem
POST
Receive Creem webhook events.
/api/upload/presigned-url
POST
Get presigned URL for client-side direct upload.
/api/upload/server-upload
POST
Server-side proxy file upload.
/api/payment-status
GET
Query payment status.
/api/keystatic/[...params]
GET, POST
Keystatic CMS API interface (development only).
5.3. Hooks and Events
useSidebar(): Used in dashboard components to control sidebar expand/collapse state.
useIsMobile(): Client-side hook to determine if current device is mobile size, safe for responsive components, avoiding SSR hydration errors.
useAdminTable(): Core hook for driving admin dashboard table components. Encapsulates data fetching, pagination, search, filtering, and loading state management logic.
onUploadComplete: Callback prop for FileUploader component, triggered after successful file upload.
Examples: Project includes unit test examples for UI components (logo.test.tsx), layouts (layout.test.tsx), schemas (auth.schema.test.ts), and core logic (database/index.test.ts).
Run Tests: pnpm test
6.2. Code Quality Assurance
ESLint: Configured in .eslintrc.json, follows eslint-config-next best practices.
Prettier: Integrated with ESLint, uses prettier-plugin-tailwindcss to auto-sort Tailwind CSS classes.
Run Checks: pnpm lint and pnpm prettier:check.
Auto Format: pnpm prettier:format.
6.3. Bundle Size Analysis
Uses @next/bundle-analyzer to analyze production build bundle size.
Run pnpm analyze to generate client and server analysis reports.
This is crucial for identifying and optimizing large dependencies.
7. Real-world Application Scenarios
7.1. Typical Use Cases
Enterprise SaaS: As starting point for new projects, integrates user management, role permissions, payments, and audit logs (through webhook events) needed by enterprises.
AI Applications: Quickly build AI tools requiring user login and usage/subscription-based payments. File upload functionality can be used for processing user data.
Paid Content Platforms: Blog and content management system combined with payment functionality can easily be extended to paid content platforms.
Internal Tools: Leverage powerful admin dashboard and data management capabilities to quickly build company internal data management tools or dashboards.
8. Utility Tools
8.1. CLI Commands
Script
Description
pnpm dev
Start development server (Turbo mode)
pnpm build
Build production application
pnpm start
Start production server
pnpm lint
Run ESLint checks
pnpm test
Run Jest unit tests
pnpm prettier:format
Format all code
pnpm db:generate
Generate migration files for development
pnpm db:generate:prod
Generate migration files for production
pnpm db:migrate:dev
Apply development migrations
pnpm db:migrate:prod
Apply production migrations
pnpm db:push
(Development only) Push schema to database
pnpm analyze
Build and analyze bundle size
pnpm set:admin
(Local) Promote user to super admin
pnpm set:admin:prod
(Production) Promote user to super admin
8.2. Configuration Options
All required and optional environment variables are detailed in the environment configuration section of README.md. Be sure to completely fill out the .env file.
8.3. Utility Functions
src/lib/utils.ts provides some useful utility functions:
cn(...inputs): Safely merge Tailwind CSS class names and resolve conflicts.
formatCurrency(amount, currency): Format amounts in cents to currency strings.
calculateReadingTime(text): Calculate estimated reading time based on text content.
renderMarkdoc(node): Convert Markdoc AST nodes to plain text strings for generating summaries.
9. Version Management & Updates
9.1. Dependency Management
Package Manager: Project uses pnpm, ensure you have it installed globally. pnpm leverages content-addressable storage to save disk space and speed up installations.
Version Locking: pnpm-lock.yaml file locks exact versions of all dependencies and their sub-dependencies, ensuring consistency across team members and different deployment environments.
Dependency Updates: Recommend using pnpm up --latest to safely update dependencies, and pay attention to major version change logs.
10. Best Practices
10.1. Performance Optimization
Code Splitting: Use next/dynamic for dynamic imports of large components, like dynamic imports for each settings page in src/app/dashboard/settings/_components/settings.tsx.
Image Optimization: Prioritize using Next.js <Image> component, which automatically performs image size optimization, format conversion (like WebP), and lazy loading.
Server Components: Use React Server Components (RSC) as much as possible for data fetching and logic execution, reducing JavaScript code sent to client.
Database Queries: Avoid executing database queries in loops. Leverage Drizzle ORM's join and batch operation capabilities to reduce database round trips.
10.2. Security Considerations
Environment Variables: Never commit .env file to Git repository. Use secret management tools from platforms like Vercel to store production environment variables.
Route Protection: middleware.ts is the first line of defense, but must use functions like requireAuth, requireAdmin in Server Actions and API routes for backend permission verification.
SQL Injection: Using Drizzle ORM effectively prevents SQL injection attacks because it automatically parameterizes queries.
XSS: Next.js and React escape JSX content by default, preventing cross-site scripting attacks. When handling user-generated content, use mature libraries (like DOMPurify) for sanitization.
Webhook Security: Signature verification in src/lib/billing/creem/webhook.ts is key to ensuring webhook requests come from trusted sources.
10.3. Deployment Guide
Vercel deployment is recommended.
Push your code to GitHub/GitLab/Bitbucket repository.
Import the Git repository in Vercel.
Vercel will automatically detect Next.js project and configure build settings.
In Vercel project's Settings > Environment Variables, add all environment variables defined in your .env file.
Configure production database migration process in your CI/CD pipeline, ensuring pnpm db:migrate:prod runs after each successful deployment.
Each push to main branch will automatically build and deploy your application on Vercel.
Create a new branch (git checkout -b feature/your-feature-name).
Make changes and commit (git commit -m 'feat: Add some feature').
Push your branch to forked repository (git push origin feature/your-feature-name).
Create a Pull Request.
12. Troubleshooting
12.1. Common Issues FAQ
Q: Why can't I access the /keystatic admin dashboard?
A: Keystatic admin interface is only enabled in development environment (NODE_ENV=development) by default for security. You need to run pnpm dev locally to access it.
Q: File upload fails with CORS error.
A: This is the most common file upload issue. Make sure you have correctly configured CORS policy in your Cloudflare R2 bucket settings, allowing PUT and GET requests from your deployment domain and http://localhost:3000.
Q: How to set up the first admin account?
A: The system doesn't automatically set up admins. You need to:
First register an account normally in the app with the email you want to make admin.
Make sure you correctly filled in the corresponding social platform's CLIENT_ID and CLIENT_SECRET in the .env file.
Make sure in the social platform's OAuth app configuration (like Google Cloud Console, GitHub Developer Settings), you've added http://localhost:3000/api/auth/[provider]/callback and your production domain's callback URL to the authorized callback URL list.
Thanks for reading!
Want to read more articles? Check out our blog for the latest insights and updates.