These docs are a work in progress and may not be fully up to date. Some pages may contain internal notes for our team.
Skip to Content

How to — deploy

Three independent deploy targets — each repo deploys on its own cadence. There is no single command that deploys all three at once. Coordinate the order based on what changed.

Deploy order for coordinated changes

When a change touches more than one repo, always deploy in this order:

  1. Backend — additive migrations first; safe to go before anything else.
  2. Widget — deploys new payload-shape consumers before the dashboard starts writing the new shape.
  3. Next.js dashboard — last, because it produces the payload fields consumers must already understand.

The cardinal rule: consumer deploys before producer when adding a field, consumer deploys after producer when removing one. This preserves backwards compatibility across the deploy gap.

⚠️ Warning: Never merge the dashboard PR before the backend deploy lands. Calling a 404 endpoint from production is worse than a short feature delay.


1. Deploy the backend (Cloudways via SSH + Makefile)

The backend (dropafinder-app-backend) deploys to Cloudways over SSH.

Run the deploy

cd "/Users/codydavis/Local Sites/dropafinder-app-backend" make deploy

make deploy is defined in Makefile as:

SSH_HOST = cloudways-dropafinder APP_PATH = /home/locationfinders-api/public_html deploy: ssh $(SSH_HOST) "cd $(APP_PATH) && \ php artisan migrate --force && \ php artisan config:cache && \ php artisan route:cache && \ php artisan view:cache"

It SSHs to cloudways-dropafinder (an alias in ~/.ssh/config) and runs four commands in sequence:

  1. migrate --force — runs any pending migrations.
  2. config:cache — bakes environment variables into the config cache.
  3. route:cache — compiles all routes to a single file.
  4. view:cache — pre-compiles Blade views.

Verify

curl -H "Authorization: Bearer $TOKEN" https://api.locationfinders.com/api/me

Expected: a 200 response with user data (not a 500 or routing error).

Cache clear (if needed)

If a deploy produces unexpected behavior, clear caches without re-running migrations:

make cache-clear

This runs config:clear, cache:clear, route:clear, and view:clear.

SSH direct access

make ssh

Drops you into an interactive SSH session on the Cloudways server for debugging.


2. Deploy the Next.js dashboard (Vercel via git push)

The dashboard (dropafinder-app-nextjs) deploys automatically on every push to main. Vercel watches the repository and triggers a build + deploy on each push.

Run the deploy

cd "/Users/codydavis/Local Sites/dropafinder-app-nextjs" git push origin main

No manual deploy command is required.

Monitor

Open the Vercel dashboard for the project to watch the build log in real time. The build typically completes in 1–3 minutes. A failed build does not affect production — Vercel keeps the last successful deployment live.

Verify

  1. Open the production URL once the Vercel build status shows “Ready”.
  2. Log in and navigate to a Finder. Open the Finder Builder.
  3. Confirm the changed feature behaves as expected in production.

💡 Tip: For changes that affect the Live Preview iframe, verify there specifically — it runs the widget inside the iframe and has a separate initialization path.


3. Deploy the widget (R2 via deploy-r2.mjs)

The widget (dropafinder-app-external/ext/2/source/) is a static JS bundle uploaded directly to Cloudflare R2. The CDN serves snippet.js from https://cdn.locationfinders.com/.

Run the deploy

cd "/Users/codydavis/Local Sites/dropafinder-app-external/ext/2/source" npm run deploy:02

This is defined in package.json as:

"deploy:02": "npm run build && node scripts/deploy-r2.mjs"

Steps it performs:

  1. npm run build — Vite production build, outputs ext/2/snippet.js and the root-level snippet.js.
  2. deploy-r2.mjs — reads R2 credentials from .env.development, then uploads two objects to R2:
    • snippet.js — root-level alias (https://cdn.locationfinders.com/snippet.js), max-age=300, must-revalidate
    • ext/2/snippet.js — versioned path, same TTL

Required env vars

Set these in .env.development (or as shell env vars) before deploying:

AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= AWS_ENDPOINT_URL_S3= # Cloudflare R2 endpoint AWS_REGION=auto CDN_BASE_URL=https://cdn.locationfinders.com

Cache propagation

The CDN TTL for snippet.js is 300 seconds (5 minutes). Visitors loading the widget within that window may still get the previous bundle. This is by design — the short TTL ensures rapid propagation without requiring a manual cache purge for routine deploys.

Verify

After 5 minutes, open a page embedding the widget in a private/incognito browser window and confirm the updated behavior is live.

🔴 [NEEDS CLARIFICATION: Is there a manual Cloudflare cache purge step for the widget deploy, or does the 5-minute TTL make it unnecessary in all cases?]


Rollback

ServiceRollback method
Backendmake sshgit loggit checkout <previous-commit> + make deploy (migrations are forward-only — coordinate if the change involved a destructive migration)
DashboardVercel dashboard → Deployments → select a previous deployment → “Promote to Production”
WidgetRe-run npm run deploy:02 from the previous git commit

Where to next