Litestore · Developer guide
Deployment

Deploying

This page helps you put a configured store into production on Vercel or any Node.js host.

Once your store works locally, you can deploy it as a single Next.js application. Use this page when you set up production for the first time or when you move to a new host.

The storefront and the admin are routes in the same app. Scheduled work runs as Inngest functions, and one route handler serves all of them. Nothing in env.ts or the runtime checks a licence.

A deploy therefore has three parts. You apply the migrations, you build and run the app, and you point Inngest at it. The rest is configuration you have already set.

Your server
One Next.js app
Storefront
Admin
Marketing
PostgresOne database
The storefront, admin and marketing tools run as one Next.js app on your server, backed by one Postgres database.

Prerequisites

A production Postgres database. It is the app's single source of truth.

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

Every required variable. See Configuration.

A verified Resend sender domain. Customers and staff sign in with 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

This script runs prisma migrate deploy. It applies the committed migrations in prisma/migrations/ and never generates new ones.

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

Local setup does not use this command. The README tells you to run bun run db:push locally instead.

Step 2: Deploy the app

Vercel

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

The package hooks build the generated files for you. postinstall runs prisma generate, which rebuilds the client on every install. prebuild runs scripts/build-icons.ts before the Next.js build.

The repository already includes a vercel.json that pins the function region:

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

Change fra1 to the region where your database runs.

Getting this wrong is expensive, and the Prisma adapter in services/db.ts is tuned for it. It uses a 30-second connection timeout, TCP keepalive and a 25-connection pool. A code comment there records round trips of 2–8 seconds to a pooler on another continent.

Every query pays that distance.

Any Node host

bun run build

This runs NODE_OPTIONS=--max-old-space-size=6144 next build. The heap limit is 6 GB, so the build machine needs that much memory.

bun run start

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

Beyond that, you need Node.js 20+ and a process manager.

Step 3: Connect background jobs

Litestore runs scheduled and deferred work as Inngest functions. The route app/api/inngest/route.ts serves them. It exports GET, POST and PUT from the serve() helper in inngest/next and sets maxDuration = 60.

The route registers these functions:

  • Abandoned carts, currency rates and payment reconciliation
  • Scheduled collection and social publishing
  • Stock and data cleanup, webhook retries and the shipment watch
  • Alert and weekly digests, plus email sends and broadcasts
  • Referral reward holds and agent verification
  • A dead-letter handler for jobs that ran out of retries
  • Everything in the spread domainEventFunctions

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

Nothing fails if Inngest is not connected

services/inngest.ts creates the client with an id and a Prisma middleware, and env.ts declares no Inngest variables. The app boots and serves pages without Inngest, but the scheduled work never runs.

Watch for this case. A deployment without Inngest looks healthy, because nothing errors. The work simply never happens.

Step 4: Verify

  1. Open Settings → Developer in the deployed admin. The self-host readiness items probe Postgres with SELECT 1 and Redis with ping(), each with a 3-second timeout. They also report whether email, media, Stripe and an AI provider are configured.
  2. Send yourself a magic link. This tests Redis, Resend and sign-in together.

Air-gapped and private deployments

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

A deployment without outbound internet access works the same as a public one. It only loses the external services you chose not to connect.

Postgres, Redis, Resend and S3 are required, though, so they must be reachable inside your network.

Troubleshooting

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

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

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

The first deploy works but every page is slow. Check that the app and the database run in the same region.

Next Steps

On this page