Deployment Guide
Tours is one Next.js application, so deploying it is the same everywhere: build it, set your environment variables, point it at PostgreSQL, connect object storage, and serve it. There is no separate API process and no background daemon to manage. This guide covers the production build, environment configuration, the database, file storage, scheduled jobs, and license activation.
Choosing where to host? See the Hosting Guide for a side-by-side of Vercel, a Node VPS, and other options.
Building for Production
From the project root:
npm install # install dependencies
npm run build # produce the optimized production build (next build)
npm run start # serve it (next start — defaults to port 3000)
On a managed platform such as Vercel, the build and start commands are configured for you by the bundled vercel.json.
The database client is lazy-initialised —
DATABASE_URLis only read at request time, not at build time. This meansnext buildsucceeds even if the database is unreachable during the build step. Just make sureDATABASE_URLis present at runtime.
Environment Variables in Production
Set the same variables documented in the Installation Guide in your host's environment configuration. The essentials for a live deployment:
DATABASE_URL="postgresql://user:password@host/dbname?sslmode=require"
JWT_SECRET="a-long-random-string"
SECRET_KEY="a-long-random-string" # encrypts stored integration secrets
NEXT_PUBLIC_SITE_URL="https://your-domain.com"
CRON_SECRET="a-random-string" # protects /api/v1/cron
Optional, depending on what you use: JWT_EXPIRES_IN, LICENSE_SERVER_URL / LICENSE_PUBLIC_KEY, ADMIN_EMAIL / ADMIN_PASSWORD, NOTIFY_EMAIL, API_DOCS_USER / API_DOCS_PASS, the R2_* storage fallbacks, PEXELS_API_KEY, and WORLDVECTORLOGO_API_KEY.
Generate strong secrets. Use
openssl rand -base64 48forJWT_SECRET,SECRET_KEY, andCRON_SECRET. Keep them stable across deploys — rotatingJWT_SECRETinvalidates active sessions, and changingSECRET_KEYafter integration credentials are saved makes those encrypted values undecryptable. SetSECRET_KEYonce, before connecting integrations.
Database (Neon / PostgreSQL)
Tours talks to PostgreSQL through the @neondatabase/serverless driver using raw, parameterized SQL. The recommended database is Neon because the serverless driver is built for it and works over HTTP, which suits serverless hosts.
- Create a Neon project and copy its connection string into
DATABASE_URL. In production, use the Neon pooler URL (...-pooler...neon.tech?sslmode=require) — pooled connections are right for serverless. - The schema is created idempotently — by the
/installwizard, bynpm run db:init, or by theensure*()helpers at runtime. There is no migration framework to run on each deploy. - Any Neon-compatible PostgreSQL endpoint that the serverless driver accepts will work.
Migrating local → Neon. For a
pg_dump/pg_restoremigration, use the direct (unpooled) endpoint (the pooler host minus-pooler) — the pooler stalls large single-transaction restores. Same database, same credentials.
File Storage (Cloudflare R2)
Uploads — tour media, images, and documents — go to object storage, never to local disk in production.
Critical for serverless hosts: on Vercel the application filesystem is read-only at runtime. The app detects this automatically (the
VERCELenvironment variable is set). Uploads must be sent to R2 (or another S3-compatible bucket); they cannot be written to the local filesystem. Configure storage before going live, or uploads will fail.
Tours's storage layer supports any S3-compatible provider — Cloudflare R2, DigitalOcean Spaces, Amazon S3, Google Cloud Storage — through a single S3 client. Configure it one of two ways:
- Recommended — Admin → Settings → Integrations → Storage. Add your provider connection (endpoint, access key, secret, bucket, public URL) and mark it Active + Default. Credentials are encrypted in the
integration_connectionstable withSECRET_KEY— they are not read from env and they come across in a DB dump. - Fallback — environment variables. Set
R2_ENDPOINT,R2_ACCESS_KEY_ID,R2_SECRET_ACCESS_KEY,R2_DEFAULT_BUCKET, andR2_PUBLIC_URL. These are used only when no active storage connection exists in the database.
To set up Cloudflare R2:
- Create an R2 bucket and a public access URL (custom domain or
r2.dev). - Generate an R2 API token with read/write access.
- Paste the account-specific endpoint, access key, secret, bucket name, and public URL into the admin (or the
R2_*env vars).
Troubleshooting uploads. An upload failing with
SignatureDoesNotMatchmeans the stored storage secret key is wrong (or the token was rotated) — it's config, not code. Re-enter the credentials in Settings → Storage. The upload route surfaces the underlying storage error rather than a generic 500, so this is diagnosable.
Scheduled Jobs (Cron)
Background tasks — payouts, reminders, cleanup — run via a single HTTP endpoint, /api/v1/cron, protected by CRON_SECRET.
On Vercel: the bundled
vercel.jsondeclares the cron for you, so Vercel calls it hourly automatically — no extra action needed:{ "crons": [{ "path": "/api/v1/cron", "schedule": "0 * * * *" }] }Self-hosted: add a crontab entry that curls the endpoint hourly. The install wizard's finish step prints the exact line, for example:
0 * * * * curl -fsS "https://your-domain.com/api/v1/cron?key=YOUR_CRON_SECRET" >/dev/null 2>&1
License Activation on the Live Domain
Tours's license is tied to your production domain.
- On a local or LAN host (localhost,
127.0.0.1,*.localhost,*.test, and private10.x/192.168.x/172.16–31.xranges) the app runs in dev mode — the license check is bypassed and all entitlements are unlocked, so you can build and test freely. - On your live domain, activate your purchase code. Do this during the install wizard's license step, or later from the admin. Activation verifies against
LICENSE_SERVER_URL(defaulthttps://creative-cape.com) using an RS256 public key — a default is baked in; override withLICENSE_PUBLIC_KEYonly if instructed.
Because dev hosts bypass licensing, always verify entitlement-gated add-ons on your real domain before launch. See the License Guide for details.
Deployment Checklist
-
DATABASE_URLset and reachable at runtime -
JWT_SECRETandSECRET_KEYset to strong, stable values -
NEXT_PUBLIC_SITE_URLset to your live URL - Storage (R2 / S3) configured — required on read-only hosts
-
CRON_SECRETset and the hourly cron wired up - License activated on the live domain
- Admin password changed from any seed value
© CreativeCape Solutions · creative-cape.com · support@creative-cape.com