VibeAny Docs

Production Operations

Run preflight, migrations, scheduled credit grants, webhook replay, and upgrades safely.

Production safety depends on knowing what each command proves. Preflight, migration execution, application validation, and end-to-end acceptance are separate steps.

What preflight checks

Run the human-readable report with:

npm run preflight

For automation, request the same redacted report as JSON:

npm run --silent preflight -- --json

Preflight checks:

  • DATABASE_URL, BETTER_AUTH_SECRET, and a non-localhost NEXT_PUBLIC_SITE_URL
  • completeness of each enabled capability, including payments, Google OAuth and One Tap, AI image generation, Turnstile, object storage, and email delivery
  • a live PostgreSQL connection
  • duplicate subscription, credit-source, and webhook-event rows that would block committed unique indexes
  • full-repository TypeScript compilation

Secret values and credentials embedded in common connection strings are redacted from the report.

Preflight does not apply migrations, lint the source, run tests, or build the application. npm run validate is also narrower than a full gate: it runs the starter's current documentation validation, i18n validation, and source lint only.

Before promotion, use the repository's CI-equivalent checks appropriate to your changes, including type checking, unit and integration tests, lint, and a production build. Provider-backed payment acceptance additionally requires a real HTTPS webhook, a sandbox payment, and database read-back.

Migration-only database changes

Schema push is prohibited in every environment:

npm run db:push

That command intentionally exits with an error. Use this workflow instead:

# Edit src/lib/db/schema.ts
npm run db:generate

# Review and commit the new drizzle/*.sql and drizzle/meta changes
npm run db:migrate

The production migration runner:

  • creates an app_migrations tracker table if needed;
  • reads committed .sql files directly from drizzle/;
  • applies unapplied files in lexical filename order;
  • executes each SQL file and its tracker insert in one PostgreSQL transaction;
  • stops immediately on tracker, SQL, or connection failure.

The runner does not provide down migrations or a distributed migration lock. Back up important data, test on a non-production database, run only one migration process against a database at a time, and use a forward-fix migration or a tested database restore if rollback is required. Never edit a migration after its filename has been recorded as applied.

The Drizzle journal is part of the generation and review history, but the production runner decides what to execute from SQL filenames and app_migrations.

Subscription credit scheduler

Vercel calls this route on the committed schedule:

5 0 * * *
GET /api/cron/subscription-credits

The job runs daily at 00:05 UTC. It does not grant credits daily. It finds subscriptions whose independently stored monthly grant boundary is due and grants the configured monthly credits.

Set a strong server-only CRON_SECRET whenever subscription products are configured. Vercel sends it as:

Authorization: Bearer <CRON_SECRET>

Route behavior:

ResultStatus
CRON_SECRET is absent503
Bearer token is absent or wrong401
Grant processing fails500
Run succeeds200 with grant and subscription counts

Monthly grants are anchored to the subscription period, use a provider-neutral idempotency source, and expire at the next grant boundary or period end. A delayed run can catch up multiple boundaries, capped at 24 grants per subscription per run. Invoice webhooks update billing state; they do not directly issue these scheduled monthly grants.

After a run, inspect function logs and read back the affected subscriptions and credit_transactions rows. A 200 with zero grants can be correct when nothing is due.

Payment webhook operations

Stripe and Creem share one application endpoint:

POST /api/payments/webhook

The active PAYMENTS_PROVIDER selects the signature verifier for new webhook traffic. The handler verifies the untouched request body, normalizes provider events, and records delivery identity in webhook_inbox before applying billing changes.

Deliveries are deduplicated by provider plus event ID or payload hash. A successfully processed replay returns 200 with deduped: true. Processing uses a two-minute inbox lease; a failed handler releases the lease when possible, and an unprocessed expired lease can be reclaimed by a later delivery.

ResponseMeaning
200, deduped: falseDelivery was accepted and processed
200, deduped: trueDelivery was already processed or is currently owned by another attempt
400Signature is missing or invalid
503The selected provider is not completely configured
500Verification succeeded but application processing failed

When replaying a failed event:

  1. Confirm the endpoint and active provider match the sender.
  2. Confirm the signing secret belongs to that exact endpoint and test/live mode.
  3. Replay from the provider dashboard or development CLI without changing the payload.
  4. Check the HTTP response and application logs.
  5. Read back webhook_inbox and the relevant subscriptions, orders, or credit_transactions rows.

For local Stripe forwarding:

stripe listen --forward-to localhost:3000/api/payments/webhook

Use the signing secret printed by that local listener only in the matching local environment.

Upgrading the starter

package.json is the canonical installed version. Upgrade customized projects deliberately:

  1. Read the changelog for every version being crossed.
  2. Commit or back up application code and database data.
  3. Apply the release changes on a separate branch instead of replacing customized files blindly.
  4. Run npm ci with the committed lockfile.
  5. Review every new SQL migration and Drizzle journal entry.
  6. Run database changes only with npm run db:migrate.
  7. Run npm run starter:check and the tests that cover your modifications.
  8. Deploy and verify a non-production environment before production.

Skipped major versions and buyer modifications can require manual integration. The starter does not automatically merge upstream changes into a customized application.

On this page