Back to templates

ShipAny Image Generator

NewOfficial
Iidoubi·Released 5 hours ago

Create stunning images with AI, in seconds.

ai-image-generatorshipany-image
Become a ShipAny Premium member to buy templates at the member price

Tech stack

Next.jsTanStackShadcn/UITailwindCSSAI SDKNode.js

About this template

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.ts
  • src/config/db/schema.postgres.ts
  • src/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:

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:

  1. Cloudflare Workers, using D1 or PostgreSQL through Hyperdrive
  2. A regular Node.js server, using SQLite, PostgreSQL, MySQL, or Turso

Cloudflare Workers is the recommended target for this template.

Deploy to Cloudflare Workers

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_SECRET and CONFIG_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_PROVIDER set to d1
  • a D1 binding named DB
  • migrations_dir set to drizzle

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.

  1. Create a Hyperdrive configuration with the direct PostgreSQL connection string:

    npx wrangler hyperdrive create my-image-generator \
      --connection-string="$DATABASE_URL"
    
  2. In wrangler.jsonc, set vars.DATABASE_PROVIDER to postgresql, remove d1_databases, and add:

    "hyperdrive": [
      {
        "binding": "HYPERDRIVE",
        "id": "the-returned-hyperdrive-id"
      }
    ]
    
  3. Put the direct PostgreSQL URL in .env.production for migration commands. Do not put it in wrangler.jsonc vars.

  4. 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
    
  5. Ensure .env.production contains DATABASE_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.

Start building your AI product

Become a ShipAny Premium member, get the ShipAny template code, and launch in an hour