Litestore · Developer guide

Deploying

Learn how to take a configured store to production: bun run db:deploy for migrations, the Next.js build, and the Inngest functions served from /api/inngest.

Litestore deploys as a single Next.js application.

The storefront and the admin are routes in the same app, scheduled work runs as Inngest functions served from one route handler, and nothing in env.ts or the runtime checks a licence.

So a deploy is three moving parts: apply the migrations, build and run the app, and point Inngest at it. Everything else is configuration you already set.

Prerequisites

A production Postgres database. The app's single store of truth.

A production Upstash Redis. REDIS_REST_URL and REDIS_REST_TOKEN are required by env.ts, and the readiness panel treats an unreachable Redis as critical.

Every required variable. See Configuration.

A verified Resend sender domain. Sign-in is a magic link, so email is the only way in.

An S3 bucket. S3_BUCKET, S3_ACCESS_KEY and S3_SECRET_ACCESS_KEY are required; uploads fail without them.

Step 1: Apply migrations

bun run db:deploy

The script is prisma migrate deploy. It applies committed migrations under prisma/migrations/ and never generates new ones.

Run it against the production database before or as part of each deploy.

Local bootstrap does not use this path; the README directs local setup at bun run db:push instead.

Step 2: Deploy the app

Vercel

Push the repository and set the environment variables in project settings.

The hooks handle the generated artifacts for you. postinstall runs prisma generate, so the client is rebuilt on every install, and prebuild runs scripts/build-icons.ts before the Next.js build.

The repository already ships a vercel.json pinning the function region:

vercel.json
{
  "$schema": "https://openapi.vercel.sh/vercel.json",
  "regions": ["fra1"]
}

Change fra1 to the region holding your database.

The Prisma adapter in services/db.ts is configured for the cost of getting this wrong: a 30-second connection timeout, TCP keepalive, and a 25-connection pool, with a code comment recording round trips of 2–8 seconds against a cross-continent pooler.

Every query pays that distance.

Any Node host

bun run build

NODE_OPTIONS=--max-old-space-size=6144 next build — a 6 GB heap ceiling, so the build host needs the memory to match.

bun run start

next start -p 3000. Put a reverse proxy in front of that port.

Node.js 20+ and a process manager are the rest of the requirement.

Step 3: Connect background jobs

Scheduled and deferred work runs as Inngest functions served from app/api/inngest/route.ts, which exports GET, POST and PUT from inngest/next's serve() with maxDuration = 60.

Registered there:

  • Abandoned carts, currency rates, payment reconciliation
  • Scheduled collection and social publishing
  • Stock and data cleanup, webhook retries, shipment watch
  • Alert and weekly digests, email send and broadcast
  • Referral reward holds, agent verification
  • A dead-letter handler for exhausted retries
  • The spread domainEventFunctions

Point an Inngest account at /api/inngest and the schedules register on first sync.

Nothing fails if Inngest is not connected

services/inngest.ts constructs the client with an id and a Prisma middleware, and env.ts declares no Inngest variables — so the app boots and serves without them. The scheduled work does not run.

That is the failure mode to watch for: an unconnected deployment looks healthy, because the missing piece is work that silently never happens rather than a request that errors.

Step 4: Verify

Open Settings → Developer in the deployed admin.

The self-host readiness items probe Postgres with SELECT 1 and Redis with ping(), each on a 3-second timeout, and report whether email, media, Stripe and an AI provider are configured.

Then send yourself a magic link, which exercises Redis, Resend and auth together.

Air-gapped and private deployments

Nothing in the codebase checks a licence, and no variable points at a Litestore service.

A deployment with no outbound internet behaves the same as a public one, minus the external services you chose not to wire up.

Postgres, Redis, Resend and S3 are required, though, so they have to be reachable inside the network.

Troubleshooting

Sign-in emails do not arrive in production. Verify the sender domain in Resend and check that RESEND_SENDER_EMAIL matches it. The readiness panel reports whether both Resend variables are set, but it does not test delivery.

Redis is down and sign-in still works. That is intended. checkRateLimit logs "Rate limiter error, failing open" and allows the request.

The Upstash client is configured with retry: { retries: 1, backoff: () => 300 }, so the failure costs one 300 ms retry instead of the SDK's five-attempt exponential backoff.

The first deploy serves but every page is slow. Check the region pairing between the app and the database.

Next Steps

On this page