Files
linsa-linsa-io/docs/production-setup.md

4.7 KiB
Raw Blame History

Production Setup (Cloudflare Workers)

This app deploys two Workers:

  • @linsa/web: SSR app + API routes + Electric proxy
  • @linsa/worker: standalone API bound in @linsa/web as WORKER_RPC

The stack expects Postgres over HTTP (Neon or a Postgres behind a Neon HTTP proxy) and ElectricSQL for sync.

Prerequisites

  • Cloudflare account with Workers enabled and wrangler logged in
  • Production Postgres reachable over HTTP (Neon recommended, or a Postgres behind a Neon HTTP proxy)
  • Electric Cloud account (or self-hosted Electric instance)
  • Domain for cookies (APP_BASE_URL)
  • OpenRouter API key (optional, for AI responses)

1) Database (Postgres)

  • Create a Neon database (recommended) and copy the postgresql://...neon.tech/... connection string.
  • Ensure logical replication (wal_level=logical) is enabled. Neon enables it by default; for other Postgres, enable it and allow replication access.
  • Electric needs replication on these tables: users, sessions, accounts, verifications, chat_threads, chat_messages.
  • If not using Neon, expose your Postgres through a Neon HTTP proxy; Cloudflare Workers cannot talk to raw TCP Postgres.

2) Electric Cloud / Self-hosted Electric

  1. Sign up at Electric Cloud or point to your own Electric instance.
  2. Create a source connected to your Postgres.
  3. Note:
    • ELECTRIC_URL Electric endpoint (shape API)
    • ELECTRIC_SOURCE_ID and ELECTRIC_SOURCE_SECRET only if Electric Cloud auth is enabled

3) Cloudflare Worker configuration

  • Optional: rename the name fields in packages/worker/wrangler.jsonc and packages/web/wrangler.jsonc. If you rename the worker, also update services[0].service in packages/web/wrangler.jsonc so the WORKER_RPC binding still points to the right script.
  • Set secrets for @linsa/web (run inside packages/web):
cd packages/web

wrangler secret put DATABASE_URL        # Neon/Postgres HTTP URL
wrangler secret put BETTER_AUTH_SECRET  # generate with: openssl rand -hex 32
wrangler secret put ELECTRIC_URL        # e.g., https://your-electric-host/v1/shape
wrangler secret put ELECTRIC_SOURCE_ID      # only if Electric Cloud auth is on
wrangler secret put ELECTRIC_SOURCE_SECRET  # only if Electric Cloud auth is on
wrangler secret put OPENROUTER_API_KEY      # optional, for real AI replies
wrangler secret put JAZZ_SPOTIFY_STATE_ID   # optional, for Spotify now playing
  • Set non-secret vars:
wrangler vars set APP_BASE_URL https://your-domain.com        # exact origin for cookies
wrangler vars set OPENROUTER_MODEL anthropic/claude-sonnet-4  # optional override
  • Prefer pnpm wrappers if you want to stay in the monorepo context:
pnpm --filter @linsa/web exec wrangler whoami

You can also run f deploy-setup from the repo root for an interactive secret setup.

4) Deploy

From the repo root:

pnpm deploy:worker   # deploy @linsa/worker
pnpm deploy:web      # build + deploy @linsa/web
# or
pnpm deploy          # deploy both
# Flow shortcut
f deploy

5) Verify

  1. Open your production URL and confirm auth flows (sign up / sign in).
  2. Create a chat thread/message; check Electric sync across two tabs.
  3. Hit /api/chat/ai to confirm OpenRouter responses (or expect the demo reply when no key is set).
  4. Tail logs if needed: pnpm --filter @linsa/web exec wrangler tail.

Environment Variables

Variable Required Description
DATABASE_URL Yes Postgres URL reachable over HTTP (Neon or Postgres behind Neon proxy)
BETTER_AUTH_SECRET Yes Secret for auth/session signing (32+ chars)
ELECTRIC_URL Yes Electric Cloud/self-host URL (shape endpoint)
ELECTRIC_SOURCE_ID Conditional Needed when Electric Cloud auth is enabled
ELECTRIC_SOURCE_SECRET Conditional Needed with ELECTRIC_SOURCE_ID
APP_BASE_URL Yes Production origin for cookies/CORS (e.g., https://app.example.com)
OPENROUTER_API_KEY No Enables real AI responses
OPENROUTER_MODEL No AI model id (default: anthropic/claude-sonnet-4)
JAZZ_SPOTIFY_STATE_ID No Jazz Spotify state id (from x/server) for now playing proxy

Troubleshooting

  • Auth: APP_BASE_URL must match your deployed origin; rotate BETTER_AUTH_SECRET only when you intend to invalidate sessions.
  • Database: use an HTTP-capable connection string; ensure logical replication is on and tables exist; allow Cloudflare egress to the DB host.
  • Electric: confirm the source is healthy and credentials are set; verify where filters in logs if shapes look empty.
  • AI chat: set OPENROUTER_API_KEY; without it youll see the demo reply instead of model output.