模板介绍
ShipAny AI Image Generator
A production-ready AI image SaaS built with TanStack Start and the ShipAny headless SaaS engine.
The repository includes the public image generator, image-to-image editing, focused image tools, user accounts, credits, subscriptions, payments, RBAC, content management, internationalization, and a complete admin console.
What is included
- AI image generation — text-to-image and image-to-image workflows
- Focused image tools — background removal, upscaling, outpainting, and style transfer
- Multiple providers — gRouter, Replicate, Fal, OpenAI-compatible endpoints, and other providers configured from the admin console
- SaaS accounts — email/password, Google/GitHub OAuth, sessions, and user settings
- Credits and billing — credit ledger, subscriptions, checkout, and payment webhooks
- Admin console — users, RBAC, payments, content, support, and system configuration
- Storage — S3-compatible and Cloudflare R2 providers
- Internationalization — English and Chinese with locale-aware URLs
- SEO — route metadata, canonical/hreflang links, Open Graph, and structured data
Tech stack
- TanStack Start, Vite 8, Nitro, React 19, and TypeScript
- TanStack Query, Form, Router, and Table
- Tailwind CSS 4 and shadcn/ui v4
- better-auth with a Drizzle adapter
- Drizzle ORM with SQLite, PostgreSQL, MySQL, Turso, and Cloudflare D1 support
- Paraglide JS for internationalization
- Cloudflare Workers, D1, R2, and Hyperdrive deployment support
Prerequisites
- Node.js 20 or newer
- pnpm
- OpenSSL, used to generate application secrets
- A database server only when using PostgreSQL or MySQL
- A Cloudflare account and Wrangler when deploying to Cloudflare Workers
Enable pnpm through Corepack if it is not already installed:
corepack enable
Local development
1. Install dependencies
pnpm install
The post-install script creates data/ and prepares the default SQLite schema
when src/config/db/schema.ts does not exist.
2. Create the local environment file
cp .env.example .env.development
Generate an auth secret:
openssl rand -base64 32
Paste the generated value into .env.development:
VITE_APP_URL=http://localhost:3000
VITE_APP_NAME=My AI Image Generator
VITE_APP_DESCRIPTION=Create and edit images with AI
VITE_APP_LOGO=/logo.svg
VITE_DEFAULT_LOCALE=en
DATABASE_PROVIDER=sqlite
DATABASE_URL=file:data/local.db
AUTH_SECRET=replace-with-the-generated-value
# Recommended: encrypt secrets saved from Admin → Settings.
# Generate this separately with `openssl rand -base64 32`.
CONFIG_ENCRYPTION_KEY=replace-with-another-generated-value
.env.development is the canonical local environment file and is ignored by
Git. An existing .env.local can override values during some commands, so
avoid keeping conflicting database or URL values in both files.
Public browser-visible variables use the VITE_ prefix. Secrets such as
AUTH_SECRET and database credentials must never use that prefix.
3. Prepare the database
For the default SQLite database:
pnpm db:setup
pnpm db:push
db:push directly synchronizes the development database and may perform
destructive changes. Use it for local development, not for production
deployments.
To use a different database locally, update .env.development before running
the commands:
| Provider | DATABASE_PROVIDER |
DATABASE_URL example |
|---|---|---|
| SQLite | sqlite |
file:data/local.db |
| PostgreSQL | postgresql |
postgresql://localhost:5432/app |
| MySQL | mysql |
mysql://localhost:3306/app |
| Turso | turso |
libsql://database.turso.io |
Turso also requires DATABASE_AUTH_TOKEN.
pnpm db:setup copies the matching dialect template into the ignored working
file src/config/db/schema.ts. Permanent schema changes must also be kept in
the appropriate committed template:
src/config/db/schema.sqlite.tssrc/config/db/schema.postgres.tssrc/config/db/schema.mysql.ts
4. Initialize RBAC and create an administrator
Create the default roles, permissions, and a local super administrator:
pnpm rbac:init \
[email protected] \
--admin-password=choose-a-local-password
Alternatively, initialize RBAC without creating a user:
pnpm rbac:init
After a user signs up, grant the user a role with:
pnpm rbac:assign \
[email protected] \
--role=super_admin
Available built-in roles are super_admin, admin, editor, and viewer.
5. Start the development server
pnpm dev
Open:
- Application: http://localhost:3000
- Sign in: http://localhost:3000/sign-in
- Admin console: http://localhost:3000/admin
- Drizzle Studio: run
pnpm db:studio
6. Configure providers
Sign in as a super administrator and open Admin → Settings. Configure the services the product needs:
- Auth — Google, GitHub, and email authentication
- Payment — Stripe, Creem, PayPal, and supported regional providers
- Email — Resend or Cloudflare Email
- Storage — Cloudflare R2 or an S3-compatible service
- AI — gRouter, Replicate, Fal, OpenAI, Anthropic, and model selection
- Analytics — Google Analytics, Plausible, and advertising settings
Provider credentials are stored in the database. When
CONFIG_ENCRYPTION_KEY is set, secret values are encrypted with AES-256-GCM.
Keep this key stable: rotating or deleting it makes existing encrypted settings
unreadable.
Environment variables with matching names can still be used as server-side fallbacks, but the admin console is the normal configuration path and database values take precedence.
7. Verify before committing
pnpm format:check
pnpm build
The production build must pass before deployment.
Database workflow
Development
After changing the schema:
pnpm db:push
Production
Generate and review a migration before applying it:
pnpm db:generate
# Inspect the generated SQL under drizzle/ before continuing.
pnpm db:migrate
Do not use db:push against a production database.
The drizzle/ directory is ignored in this product repository, so make sure
your deployment process preserves or packages the generated migration files.
Production deployment
The project supports two primary deployment targets:
- Cloudflare Workers, using D1 or PostgreSQL through Hyperdrive
- A regular Node.js server, using SQLite, PostgreSQL, MySQL, or Turso
Cloudflare Workers is the recommended target for this template.
Deploy to Cloudflare Workers
Recommended: use the deployment skill
In Claude Code or another compatible agent environment, run:
/deploy-cloudflare
The project skill performs the full idempotent deployment flow:
- verifies the local build and Wrangler login
- creates or reuses D1/Hyperdrive resources
- generates and applies migrations
- seeds RBAC
- creates and uploads
AUTH_SECRETandCONFIG_ENCRYPTION_KEY - configures the production URL
- asks for confirmation before the final deployment
- optionally creates or promotes the first
super_admin - smoke-tests the deployed application
Re-running the skill updates the existing deployment without recreating resources.
Manual Cloudflare deployment with D1
The following commands assume macOS or Linux.
1. Authenticate and create the working configuration
npx wrangler login
cp wrangler.example.jsonc wrangler.jsonc
wrangler.jsonc is intentionally ignored by Git because it contains the real
resource IDs and deployment URL. Do not edit generated files under .output/.
2. Create the D1 database
npx wrangler d1 create my-image-generator-db
Update wrangler.jsonc with:
- your Worker name
- the returned D1
database_id - the chosen
database_name vars.DATABASE_PROVIDERset tod1- a D1 binding named
DB migrations_dirset todrizzle
3. Configure the production URL
Create .env.production:
VITE_APP_URL=https://my-image-generator.<your-subdomain>.workers.dev
VITE_APP_NAME=My AI Image Generator
Set the same VITE_APP_URL and VITE_APP_NAME under vars in
wrangler.jsonc. The URL must be correct in both places because it is used for
trusted auth origins, callbacks, canonical URLs, and server-side runtime
fallbacks.
For a custom domain, add:
"routes": [
{
"pattern": "images.example.com",
"custom_domain": true
}
]
Then use https://images.example.com as VITE_APP_URL. The domain or its
parent zone must already be in the same Cloudflare account.
4. Generate and apply D1 migrations
DATABASE_PROVIDER=d1 pnpm db:setup
pnpm db:generate
Review the SQL under drizzle/, then apply it remotely:
npx wrangler d1 migrations apply my-image-generator-db --remote
5. Seed RBAC
pnpm rbac:init cannot connect directly to a remote D1 binding. Seed a local
Wrangler D1 database, export the RBAC rows, and import them remotely:
npx wrangler d1 migrations apply my-image-generator-db --local
LOCAL_D1=$(find .wrangler/state -name "*.sqlite" -path "*d1*" | head -1)
DATABASE_PROVIDER=sqlite DATABASE_URL="file:$LOCAL_D1" pnpm rbac:init
sqlite3 "$LOCAL_D1" ".dump role permission role_permission" \
| grep "^INSERT INTO" \
| sed 's/^INSERT INTO/INSERT OR IGNORE INTO/' \
> /tmp/rbac-seed.sql
npx wrangler d1 execute my-image-generator-db \
--remote \
--file=/tmp/rbac-seed.sql
The sqlite3 CLI is included with macOS. On Debian/Ubuntu, install it with
apt-get install sqlite3.
6. Add Worker secrets
Never place secrets in wrangler.jsonc vars; Worker vars are visible
configuration, not secret storage.
openssl rand -base64 32 | npx wrangler secret put AUTH_SECRET
openssl rand -base64 32 | npx wrangler secret put CONFIG_ENCRYPTION_KEY
Keep CONFIG_ENCRYPTION_KEY stable after provider settings have been saved.
7. Build and deploy
Always use the provided deployment command:
pnpm cf:deploy
This command loads .env.production, builds with
NITRO_PRESET=cloudflare_module, merges the root wrangler.jsonc into the
generated Nitro configuration, and runs wrangler deploy.
After the first deployment, update .env.production and wrangler.jsonc if
Wrangler assigned a different workers.dev URL, then run pnpm cf:deploy
again.
8. Create the first production administrator
The easiest option is to let /deploy-cloudflare create the account.
For a manual deployment, sign up through /sign-up, then promote the user:
npx wrangler d1 execute my-image-generator-db --remote --command="
INSERT OR IGNORE INTO user_role (id, user_id, role_id)
SELECT lower(hex(randomblob(16))), u.id, r.id
FROM user u, role r
WHERE u.email = '[email protected]'
AND r.name = 'super_admin'
"
Sign out and sign in again after changing a user's role.
9. Configure R2 storage
The local upload fallback writes files to public/uploads. Cloudflare Workers
does not provide a persistent writable filesystem, so production image upload
requires R2 or another public S3-compatible storage service.
Configure it at Admin → Settings → Storage and verify the public domain before enabling image generation.
10. Verify the deployment
curl -I https://your-production-url.example/
curl -I https://your-production-url.example/api/config/public
Both endpoints should return a successful response.
PostgreSQL on Workers with Hyperdrive
Use this option when an existing PostgreSQL database is preferred over D1.
-
Create a Hyperdrive configuration with the direct PostgreSQL connection string:
npx wrangler hyperdrive create my-image-generator \ --connection-string="$DATABASE_URL" -
In
wrangler.jsonc, setvars.DATABASE_PROVIDERtopostgresql, removed1_databases, and add:"hyperdrive": [ { "binding": "HYPERDRIVE", "id": "the-returned-hyperdrive-id" } ] -
Put the direct PostgreSQL URL in
.env.productionfor migration commands. Do not put it inwrangler.jsoncvars. -
Prepare and migrate the database:
set -a . ./.env.production set +a DATABASE_PROVIDER=postgresql pnpm db:setup DATABASE_PROVIDER=postgresql pnpm db:generate DATABASE_PROVIDER=postgresql pnpm db:migrate DATABASE_PROVIDER=postgresql pnpm rbac:init -
Ensure
.env.productioncontainsDATABASE_PROVIDER=postgresql, then deploy:pnpm cf:deploy
At runtime, the application reads the connection string from the binding named
exactly HYPERDRIVE. Schema migrations still connect directly to PostgreSQL;
they do not run through Hyperdrive.
Deploy to a Node.js server
Use a Node deployment for a VPS, container platform, or any host that can run a persistent Node process.
PostgreSQL, MySQL, or Turso is recommended for multi-instance production deployments. SQLite is suitable only for a single instance with persistent disk.
1. Create .env.production
At minimum:
VITE_APP_URL=https://images.example.com
VITE_APP_NAME=My AI Image Generator
DATABASE_PROVIDER=postgresql
DATABASE_URL=postgresql://database-host:5432/app
AUTH_SECRET=replace-with-a-production-secret
CONFIG_ENCRYPTION_KEY=replace-with-a-stable-encryption-key
Set these values in the hosting platform's secret manager when possible.
VITE_APP_URL must be present during the build because it is embedded in
client metadata and auth configuration.
2. Generate and apply production migrations
Load the production environment, generate the migration, review it, and apply it:
set -a
. ./.env.production
set +a
NODE_ENV=production pnpm db:setup
NODE_ENV=production pnpm db:generate
NODE_ENV=production pnpm db:migrate
NODE_ENV=production pnpm rbac:init
3. Build
NODE_ENV=production pnpm build
4. Start
The runtime process must receive the same production environment:
set -a
. ./.env.production
set +a
NODE_ENV=production PORT=3000 pnpm start
In production, run this command through a process manager or your hosting platform. Terminate TLS at the reverse proxy and forward requests to the configured port.
5. Verify
curl -I https://images.example.com/
curl -I https://images.example.com/api/config/public
Environment variable rules
| Variable | Required | Purpose |
|---|---|---|
VITE_APP_URL |
Yes | Public application URL, auth origin, callbacks, and SEO URLs |
VITE_APP_NAME |
Yes | Product name shown in the UI and metadata |
DATABASE_PROVIDER |
Yes | sqlite, postgresql, mysql, turso, or d1 |
DATABASE_URL |
Node/local | Direct database connection string |
AUTH_SECRET |
Yes | better-auth signing/encryption secret |
CONFIG_ENCRYPTION_KEY |
Recommended | Encrypts provider secrets stored in the config table |
AUTH_TRUSTED_ORIGINS |
Optional | Extra comma-separated auth origins |
VITE_DEFAULT_LOCALE |
Optional | Default locale, normally en |
Do not expose server secrets through VITE_* variables.
Common deployment problems
| Problem | Cause | Fix |
|---|---|---|
| Every production request returns 500 | AUTH_SECRET is missing or still a placeholder |
Set a real secret in the runtime environment or with wrangler secret put |
Sign-in returns 403 Invalid origin |
The production URL was not present during the build or does not match runtime config | Keep .env.production and wrangler.jsonc VITE_APP_URL synchronized, then rebuild |
D1 binding "DB" not found |
Missing binding, wrong binding name, or placeholder database ID | Configure d1_databases with binding DB and a real ID |
| Image upload fails on Workers | The local filesystem fallback is unavailable | Configure R2/S3 in Admin → Settings → Storage |
| Cloudflare reports a stubbed DB driver | The build-time provider differs from wrangler.jsonc |
Correct vars.DATABASE_PROVIDER and run pnpm cf:deploy again |
| Admin returns 403 after a role change | The current session still has old claims | Sign out and sign in again |
| A custom domain deployment fails with “not a zone” | The domain is not managed by the current Cloudflare account | Add the zone or deploy to workers.dev |
For live Worker errors:
npx wrangler tail
Useful commands
| Command | Description |
|---|---|
pnpm dev |
Start the development server on port 3000 |
pnpm build |
Build the Node production bundle |
pnpm start |
Start the generated Node server |
pnpm cf:build |
Build with the Cloudflare module preset |
pnpm cf:deploy |
Build and deploy to Cloudflare Workers |
pnpm db:setup |
Copy the schema template for the selected database |
pnpm db:push |
Synchronize a development database directly |
pnpm db:generate |
Generate production migration SQL |
pnpm db:migrate |
Apply production migrations to a direct database |
pnpm db:studio |
Open Drizzle Studio |
pnpm rbac:init |
Seed default roles and permissions |
pnpm rbac:assign |
Assign a role to an existing user |
pnpm format |
Format the repository |
pnpm format:check |
Check formatting |
Project structure
src/
├── blocks/ # Product and landing-page sections
├── components/ # Reusable UI and shadcn primitives
├── config/ # Environment, database schemas, and locale config
├── content/pages/ # MDX content pages
├── core/ # Auth, database, AI, payment, email, and storage
├── hooks/ # Shared client data hooks
├── lib/ # API client and application utilities
├── modules/ # Business services
└── routes/ # Pages and API routes
messages/
├── en.json # English messages
└── zh.json # Chinese messages
drizzle/ # Generated migration SQL
wrangler.example.jsonc # Committed Cloudflare configuration template
wrangler.jsonc # Ignored deployment-specific working copy
License
This is proprietary software. See LICENSE for the full license agreement.
Built with ShipAny.
