Test script: Deploy your app to your own domain
The script we wrote before testing, with every amendment and its timestamp. The verdict it produced: Put your vibe-coded app on your own domain: Cloudflare Pages vs Vercel.
Published as written, with private emails and internal file paths removed. Times are Eastern. How we score: the rubric.
- Job slug:
deploy-to-own-domain - Version / status: v1 · frozen 2026-10-16 00:47 ET, before the first run. Checker:
checks/run-checks-deploy.mjsat bwt-auth-starter commitf2b4b4f(branchdeploy-base). - Run order for v1: Vercel and Cloudflare Pages now. Netlify waits for written permission (no sign-up). Render is a later fallback.
- Rubric version: v1 (rubric)
- Author / date: Lando (CEO), 2026-10-16
- Conflict to disclose: BuildWithThis itself is hosted on Vercel (Hobby). The page must say so.
1. The job
You have a working app in a GitHub repo, a frontend plus a small Node API, like most AI-built apps once they leave the builder. You want it live on your own domain with HTTPS, without becoming a DNS expert. You’re done when https://<sub>.buildwiththis.xyz serves the app and its API with a valid certificate.
2. Candidates
Picked because they’re what vibe coders actually use. Bolt.new deploys to Netlify. v0 and Lovable’s export docs point to Vercel. Cloudflare is the common “free and fast” choice.
| Tool | Plan for the test | Sign-in / account | ToS check (2026-10-16) | Run mode |
|---|---|---|---|---|
| Vercel | Hobby (free), existing team architp8-5831 | Already connected (Vercel connector; GitHub app already linked through buildwiththis.com) | AUP: non-Enterprise customers “may conduct benchmark tests of a service (other than AI Services)… publicly disclosed results must include all necessary information for others to replicate the tests.” Hosting isn’t an AI Service, so this test is allowed if we publish replication details (this script and the repo tag). The AUP bans using automation to circumvent rate limits or create multiple accounts; the official API and CLI are fine. ⚠️ Terms §4: Hobby is for “personal or non-commercial use” only. | Agent-driven (API/CLI/connector) |
| Cloudflare (Workers & Pages) | Free, existing account [private email removed] | Already connected (Cloudflare connector) | Self-serve terms §2.2.2: “You are permitted to perform benchmark tests of our Services.” If we publish, we must include everything needed to replicate the test, and Cloudflare may publish its own benchmarks of our services. No automation clause beyond creating multiple accounts or scraping. No commercial-use limit on the free plan found. | Agent-driven |
| Netlify | Free | Needs an account (sign up with [email protected], or the user’s GitHub) | ⚠️ AUP: “Users must not perform any benchmark tests or analyses relating to Netlify Services or website without express permission of Netlify.” Automated access is allowed only through Netlify’s API. Terms keyword scan: no commercial-use limit found. Blocked until Netlify gives written permission. | Agent-driven via API/CLI, only after permission |
| Fallback: Render | Free | Needs an account | Terms explicitly allow “artificial intelligence-based coding assistants” as Authorized Third-Party Tools. AUP bans scraping, not benchmarks. No benchmark clause found. | Use only if Netlify says no or hasn’t replied before the run |
3. Fixed inputs
-
App: private repo
pathakarchit/bwt-auth-starter, tagdeploy-v1(commit9dc00d2, which isstarter-v1plus 8 lines):- The footer shows the build-time env
VITE_APP_LABEL. GET /api/healthreturns{ ok, label: APP_LABEL, canarySet }.APP_CANARYis a server-only value that must never reach the browser.
No auth, so this tests deploying, not login. Notes are stored in a JSON file; on serverless hosts that may be read-only or temporary. Persistence is not scored here (that’s job #4).
- The footer shows the build-time env
-
Env values (identical for every tool):
VITE_APP_LABEL=j03-<tool>APP_LABEL=j03-<tool>-runtimeAPP_CANARY=bwt-canary-<random 16 hex, per run, kept in .env.test>
-
Domain:
buildwiththis.xyz. DNS is at Spaceship, which only the user can log in to (passkey). Apex currently hasA 76.76.21.21(Vercel); no CAA records. Test hostnames:- Vercel:
j03-vercel.buildwiththis.xyz - Cloudflare:
j03-cf.buildwiththis.xyz - Netlify:
j03-netlify.buildwiththis.xyz(Render fallback:j03-render.buildwiththis.xyz)
- Vercel:
-
Project names (checked free on 2026-10-16): Cloudflare
bwt-j03(→bwt-j03.pages.dev), Netlifybwt-j03(→bwt-j03.netlify.app). Vercel:bwt-j03. -
Checker:
checks/run-checks-deploy.mjs(added before the first run), using Node fetch,tls.connectfor the certificate, and DNS over HTTPS (dns.google) for record visibility. No browser is needed except one screenshot of the page per tool.
4. Fixed task (the “prompt”)
The operator (agent) follows only the vendor’s official docs and logs each docs page used.
Deploy the app at tag deploy-v1 so that:
1. https://<host>.buildwiththis.xyz serves the frontend with a valid HTTPS certificate,
and plain http:// redirects to https://.
2. The Node API runs on the same host: GET /api/health and GET /api/notes return 200 JSON.
3. VITE_APP_LABEL is applied at build time (visible in the footer) and APP_LABEL / APP_CANARY
at runtime (health shows the label and canarySet=true). APP_CANARY must not appear in any
file the browser downloads.
4. Pushing a new commit to the deploy branch redeploys automatically, or with one documented command.
5. You can roll back to the previous deployment.
Use the vendor's recommended way to deploy a Vite + Express app. If the API needs code changes
for this host, make the smallest change the docs show.
5. Run design (keeps user steps to one DNS batch)
Phase 0, setup (agent, before the user’s DNS step; time logged but not scored as “time to live”):
- Create each project and deploy it to the vendor’s own URL (
*.vercel.app,*.pages.dev,*.netlify.app). - Attach the custom hostname in each vendor’s dashboard, API or CLI first. Cloudflare’s docs warn that a CNAME without this step gives a 522 error, and attaching first also avoids dangling records.
- Copy the exact DNS values each vendor asks for. Vercel gives a project-specific CNAME target, so it’s only known after this step. Record any extra TXT verification records requested.
- Send the user one batch of records (section 5a).
Phase 1, user step (one time): the user adds the batch at Spaceship and replies “done” with the time. That is the only planned user step for all three tools. A second batch, if any vendor needs one, counts as a manual intervention for that vendor.
Phase 2, timed checks (agent): for each host, poll public DNS every 30 s until the record is visible, then measure time from DNS visible to valid HTTPS (T_dns→https). Then run all checks in section 7.
5a. The DNS batch (template; exact values are filled in after phase 0)
| Type | Host (at Spaceship) | Value | For |
|---|---|---|---|
| CNAME | j03-vercel | <project-specific>.vercel-dns-0xx.com (from Vercel’s Domains settings) | Vercel |
| CNAME | j03-cf | bwt-j03.pages.dev | Cloudflare Pages |
| CNAME | j03-netlify | bwt-j03.netlify.app | Netlify (or j03-render → <service>.onrender.com for the fallback) |
| TXT | only if a vendor asks (e.g. _vercel, netlify-challenge, or Cloudflare certificate validation) | value shown by the vendor | Counted as an extra DNS step for that vendor |
TTL: Spaceship default. Don’t change the apex record or the MX records.
6. Follow-up rules and interventions
- Time cap: 90 active agent minutes per tool, not counting waiting on the user’s DNS step. DNS→HTTPS wait cap: 60 minutes after the record is visible.
- Allowed: vendor docs, CLI and API references, error messages.
- Not allowed: third-party tutorials, copying another tool’s branch.
- Manual interventions (C2):
- Any action only the user can do, other than the one planned DNS batch: OAuth/GitHub-app installs, extra DNS records, dashboard-only clicks the agent can’t make, support tickets.
- Any workaround not in the docs.
- Sign-up for a missing account is setup, logged but not scored. It must use tools@ or [email protected].
- Deploy method: Git-connected deploys from a branch
<tool>cut fromdeploy-v1, which is what readers do. If the vendor’s GitHub app isn’t installed, installing it is a user step and is logged. A CLI direct upload is allowed as a fallback and logged.
7. Pass/fail checks
| ID | Check | How to test | Pass if | Evidence |
|---|---|---|---|---|
| D1 [core] | Live on own domain | GET https://<host>/ | 200; page title “Notes”; footer Build: j03-<tool> | HTTP log, 1 screenshot |
| D2 [core] | Valid HTTPS | tls.connect to <host>:443 with SNI | Certificate valid now, SAN covers <host>, chain trusted; issuer recorded | Cert JSON |
| D3 [core] | HTTP → HTTPS | GET http://<host>/ without following redirects. The box blocks outbound port 80, so if the checker logs NOT-RUN-FROM-BOX, run it from an external HTTP checker reachable over HTTPS and log the tool and raw response | 301/308 to https://<host>/ | HTTP log |
| D4 [core] | API on same host | GET /api/health, GET /api/notes | Both 200 JSON; health label = j03-<tool>-runtime, canarySet = true | HTTP log |
| D5 [core / G2] | No server secret in browser files | Download / plus every JS/CSS asset it references; search for the APP_CANARY value | Not found | grep output |
| D6 | Redeploy on push | Push a commit that changes VITE_APP_LABEL to j03-<tool>-v2 (env change plus an empty commit) | New deploy goes live with no manual step; record the time from push to live | Timeline |
| D7 | Rollback | Use the vendor’s rollback / promote-previous feature | Footer shows j03-<tool> again; record the time | Timeline |
| D8 | SPA fallback / 404 | GET /does-not-exist | Clean 404 or app shell, not a platform error page; recorded either way (reported, not scored) | HTTP log |
| D9 | Response time (reported, not scored) | 10 sequential GET / and /api/health from the box after 1 warm-up; median and p90 | Reported with location and time, so others can repeat the test | JSON |
Metrics logged (not pass/fail):
- Phase-0 agent time to the vendor URL.
- DNS records requested (count and types) and user steps.
- T_dns→https.
- Lines changed (
git diff --shortstat deploy-v1 -- . ':!package-lock.json') and vendor-specific files added. - Docs pages used.
- Plan and cost.
8. Quality checklist (C5): docs and developer experience
| ID | Item | Pass if |
|---|---|---|
| Q1 | Official guide for Vite (React) plus a Node/Express API exists | Yes, and it works as written |
| Q2 | Custom-domain docs cover subdomains on external DNS | Yes, with the exact record type and value |
| Q3 | Dashboard/API shows the DNS values to set and the certificate status | Yes |
| Q4 | Errors hit were explained by the docs or the error message | Yes |
| Q5 | Free-tier limits and the commercial-use policy are clearly stated | Yes, on the pricing page or in the terms |
9. Security checklist (C7)
| ID | Item | How to test | Pass if |
|---|---|---|---|
| S1 | HTTPS by default | D2 + D3 | Automatic certificate and redirect with no extra config |
| S2 | HSTS | Response headers on the custom domain | Strict-Transport-Security present by default (reported; pass/fail for C7) |
| S3 | Server env stays server-side | D5 | Pass |
| S4 | Preview deployments | Open a preview URL while signed out | Protected by default, or the docs clearly warn that previews are public |
10. Weights and tie-breaker
- Weights: default (C1 35 · C2 15 · C3 10 · C4 10 · C5 10 · C6 10 · C7 10).
- C1: core checks D1–D5 decide G1; C1 uses D1–D7.
- C2 anchors: user steps beyond the planned DNS batch plus docs-unanswered problems. 5 = 0–1 · 4 = 2–3 · 3 = 4–5 · 2 = 6–8 · 1 = more.
- C3 anchors: agent active time to all core checks passing (T = 90 min): 5 = ≤22 · 4 = ≤45 · 3 = ≤67 · 2 = ≤90 · 1 = cap hit. T_dns→https is reported separately.
- C4: 5 = free plan allows this use, with quota to spare · 4 = free, but the plan’s terms limit it (e.g. non-commercial only) · 2 = needs a paid plan.
- C6 (lock-in): 5 = no vendor-specific code (config file only), custom domain on free · 3 = API had to be rewritten for this host’s runtime · 1 = can’t leave without a rewrite.
- Tie-breaker: C2 (fewest user steps), then C3.
11. Cleanup
- Keep the projects and DNS records until the verdict is published plus 30 days.
- Then delete the projects first, then the CNAMEs, so no records are left dangling.
- Rotate
APP_CANARYper run. It’s not a real secret.
Notes at freeze
- Netlify permission: ask Netlify for written permission to benchmark and publish, from [email protected]. CEO decision 2026-10-16: Netlify waits for that permission and is not part of the first runs; Render is a later fallback.
- Cloudflare product choice (v1 decision: Pages): Cloudflare now steers new projects to Workers. Workers custom domains and routes need the zone on Cloudflare, which we’re not doing until the planned DNS move. Follow the docs: if the recommended path needs nameservers on Cloudflare, log it as a finding and use Pages (CNAME to
*.pages.dev, per Pages custom domains). - Vercel Hobby commercial-use limit: the run uses the existing Hobby team. The page must tell readers that a commercial app needs a paid plan, and we should decide whether buildwiththis.com itself needs to move off Hobby.