⚡ Minimal authenticated starter kit for building apps on Cloudflare Workers.
- Authentication - better-auth with email/password + Google OAuth
- User Settings - Profile, password, theme preferences
- Dashboard Layout - Responsive sidebar navigation
- UI Components - Full shadcn/ui component library
- Component Showcase - Reference page for all available components
- Theme System - Dark/light/system mode support
- API Structure - Hono backend with auth middleware
- Database - Cloudflare D1 with Drizzle ORM
| Layer | Technology |
|---|---|
| Platform | Cloudflare Workers with Static Assets |
| Frontend | React 19 + Vite |
| Backend | Hono |
| Database | D1 (SQLite) + Drizzle ORM |
| Auth | better-auth |
| UI | Tailwind v4 + shadcn/ui |
| Data Fetching | TanStack Query |
| Forms | React Hook Form + Zod |
git clone https://github.com/jezweb/vite-flare-starter.git my-app
cd my-app
pnpm install# Login to Cloudflare
pnpm cf:login
# Create D1 database
npx wrangler d1 create vite-flare-starter-db
# Copy the database_id to wrangler.jsonc
# Create R2 bucket for avatars
npx wrangler r2 bucket create vite-flare-starter-avatars# Copy example env file
cp .dev.vars.example .dev.vars
# Edit .dev.vars with your values:
# - BETTER_AUTH_SECRET (generate with: openssl rand -hex 32)
# - BETTER_AUTH_URL (http://localhost:5173 for local)
# - Optional: Google OAuth credentials# Generate migration from schema
pnpm db:generate:named "initial_schema"
# Apply migration locally
pnpm db:migrate:localpnpm dev
# Open http://localhost:5173# Apply migration to remote database
pnpm db:migrate:remote
# Set production secrets
echo "your-secret" | npx wrangler secret put BETTER_AUTH_SECRET
echo "https://your-app.workers.dev" | npx wrangler secret put BETTER_AUTH_URL
# Deploy
pnpm deployBefore deploying to a new domain, ensure:
-
Set
TRUSTED_ORIGINSto include your production domain(s):echo "http://localhost:5173,https://your-app.workers.dev" | npx wrangler secret put TRUSTED_ORIGINS
-
Set
BETTER_AUTH_URLsecret to your exact production URL:echo "https://your-app.workers.dev" | npx wrangler secret put BETTER_AUTH_URL
-
For Google OAuth, add redirect URI in Google Cloud Console:
https://your-app.workers.dev/api/auth/callback/google
Common issues:
- Auth works but redirects to homepage → Check
TRUSTED_ORIGINSincludes your domain - OAuth callback fails → Check
BETTER_AUTH_URLmatches your domain exactly - Google sign-in fails → Check redirect URI is registered in Google Cloud Console
vite-flare-starter/
├── src/
│ ├── client/ # Frontend (React SPA)
│ │ ├── components/ui/ # shadcn/ui components
│ │ ├── layouts/ # DashboardLayout
│ │ ├── modules/
│ │ │ ├── auth/ # Sign-in/sign-up pages
│ │ │ └── settings/ # User settings module
│ │ ├── pages/ # Route pages
│ │ └── lib/ # Utilities
│ ├── server/ # Backend (Hono API)
│ │ ├── modules/
│ │ │ ├── auth/ # Auth configuration
│ │ │ ├── settings/ # Settings routes
│ │ │ └── api-tokens/ # API token management
│ │ ├── middleware/ # Auth middleware
│ │ └── db/schema.ts # Central schema exports
│ └── shared/ # Shared code (Zod schemas)
├── drizzle/ # Database migrations
├── wrangler.jsonc # Cloudflare Workers config
└── vite.config.ts # Vite build config
- Create Backend - Add routes in
src/server/modules/your-module/ - Create Schema - Add Drizzle table in
src/server/modules/your-module/db/schema.ts - Export Schema - Add export to
src/server/db/schema.ts - Generate Migration - Run
pnpm db:generate:named "add_your_table" - Register Routes - Mount in
src/server/index.ts - Create Frontend - Add pages/hooks/components in
src/client/modules/your-module/ - Add Route - Update
src/client/App.tsx
Set DISABLE_EMAIL_SIGNUP=true to prevent new email/password registrations:
# .dev.vars (local)
DISABLE_EMAIL_SIGNUP=true
# Production
echo "true" | npx wrangler secret put DISABLE_EMAIL_SIGNUPNote: This only affects email/password signup:
- Existing email users can still log in
- Google OAuth is NOT affected (domain restriction is handled at Google Cloud level)
- Create OAuth credentials at Google Cloud Console
- Set authorized redirect URI:
https://your-app.workers.dev/api/auth/callback/google - Add credentials to
.dev.varsand production secrets
Domain Restriction: To allow only your Google Workspace domain:
- Go to OAuth consent screen → Set "User type" to Internal
- Only users from your domain can sign in (e.g., @yourcompany.com)
The starter includes 8 built-in color themes plus support for custom themes.
Using Theme Generators:
- Visit a theme generator:
- tweakcn - Modern editor with OKLch support
- shadcn/ui Themes - Official hand-picked themes
- 10000+ Themes - Browse community themes
- Copy the generated CSS
- Go to Settings → Color Theme → Custom
- Paste the CSS and click "Apply Theme"
Using Claude Code:
Ask Claude Code to generate a theme based on your brand:
Create a custom theme for my app using brand colors:
- Primary: #2563eb (blue)
- Make it professional and clean
Claude will generate the CSS variables which you can paste into the Custom Theme dialog.
Theme CSS Format:
:root {
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--primary: 220 90% 56%;
/* ... other variables */
}
.dark {
--background: 240 10% 3.9%;
/* ... dark mode variables */
}Customize the app name displayed in the sidebar:
# .dev.vars (local)
VITE_APP_NAME=My Client App
# Production (in wrangler.jsonc vars or secrets)Set the default color theme for new users:
# .dev.vars (local)
VITE_DEFAULT_THEME=blue # Options: default, blue, green, orange, red, rose, violet, yellowOr edit src/shared/schemas/preferences.schema.ts directly for additional control.
To prevent users from changing the color theme (useful for branded client sites):
# .dev.vars (local)
VITE_FEATURE_THEME_PICKER=falseNote: Users can still switch between light/dark/system mode - only the color theme picker is hidden.
To hide the API Tokens tab from regular users (power user feature):
# .dev.vars (local)
VITE_FEATURE_API_TOKENS=falseConfigure allowed origins for authentication (required for production):
# .dev.vars (local) - comma-separated list
TRUSTED_ORIGINS=http://localhost:5173,https://myapp.workers.dev,https://myapp.comNote: http://localhost:5173 is always included automatically for development.
Sensitive endpoints are rate-limited to prevent abuse:
| Endpoint | Limit | Window |
|---|---|---|
| Password change | 3 | 24 hours |
| Email change | 5 | 24 hours |
| Account deletion | 1 | 24 hours |
| Avatar upload | 10 | 1 hour |
| API token creation | 10 | 24 hours |
Rate limit constants can be configured in src/shared/config/constants.ts.
Note: Rate limiting uses in-memory storage per worker instance. For distributed rate limiting across workers, consider upgrading to KV storage.
All responses include security headers:
X-Content-Type-Options: nosniffX-Frame-Options: DENYReferrer-Policy: strict-origin-when-cross-originPermissions-Policy: camera=(), microphone=(), geolocation=()- HSTS in production
Users can reset their password via email. Requires email configuration:
# .dev.vars
EMAIL_API_KEY=re_xxxxx # Resend API key
EMAIL_FROM=noreply@yourdomain.comThe flow:
- User visits
/forgot-password - Enter email → reset link sent
- Click link →
/reset-password?token=xxx - Set new password → redirect to sign-in
Users can view and revoke active sessions at Settings → Sessions:
- View all logged-in devices/browsers
- See IP address and last active time
- Revoke individual sessions
- "Log out everywhere" to revoke all other sessions
Run tests with Vitest:
pnpm test # Run tests once
pnpm test:watch # Watch modeTests use @cloudflare/vitest-pool-workers to run against Miniflare for realistic D1/R2 simulation.
Populate development data:
pnpm db:seedCreates test users:
test@example.com/password123admin@example.com/admin12345
Plus sample API tokens and organization settings.
A centralized API client is available at src/client/lib/api-client.ts:
import { apiClient } from '@/client/lib/api-client'
// Type-safe requests with automatic credentials
const data = await apiClient.get<User>('/api/user')
const result = await apiClient.post<Result>('/api/items', { name: 'New Item' })MIT - see LICENSE