From hypervibe
Guides connecting a custom domain to a Vercel-deployed app via Cloudflare DNS + Email Routing, with optional Resend email setup. Supports 10+ registrars.
How this skill is triggered — by the user, by Claude, or both
Slash command
/hypervibe:add-domainThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
You guide the user to connect a domain name to their Vercel app. The target architecture: **<Registrar> -> Cloudflare (DNS + Email Routing) -> Vercel (hosting)**.
You guide the user to connect a domain name to their Vercel app. The target architecture: -> Cloudflare (DNS + Email Routing) -> Vercel (hosting).
Cloudflare is needed for Email Routing (receiving emails at [email protected] redirected to a real mailbox) AND to benefit from their fast DNS + free DDoS protection.
This skill (and the _dns-* ones it calls) reads the Cloudflare token + the registrar token from the vault, so first make sure it is unlocked (follow _ensure-vault): node "${CLAUDE_SKILL_DIR}/../../scripts/vault/vault.mjs" status -> if locked/expired, run launch.mjs unlock; if the vault does not exist, delegate to _add-keyring.
Invoke the internal skill _detect-project-root to retrieve PROJECT_NAME, WEB_DIR, IS_NEXTJS. Abort if IS_NEXTJS=no.
Check that the project is indeed deployed on Vercel:
vercel ls 2>/dev/null | head -5
If the project is not yet deployed, tell the user to deploy first (say "deploy"; or /bootstrap if the app does not exist yet).
Do you already have a domain name, or do you need to buy one?
I recommend Hostinger (French UI/support/billing, .fr supported, and it's the registrar we automate best in this plugin).
- Go to https://www.hostinger.fr/achat-nom-de-domaine
- Search for the domain you want (e.g.
myproject.fr)- Add it to the cart and complete the purchase
.fr: ~6-10 EUR/year -.com: ~10-15 EUR/yearLet me know when it's done + the exact domain name.
-> When the purchase is confirmed, the registrar is Hostinger. Move to Step 4 with registrar=hostinger.
Ask:
What is the exact domain name?
Then Step 3.
Which registrar is this domain registered with?
- Hostinger (FR-friendly, official MCP)
- Cloudflare (registrar + DNS in the same place)
- OVH (FR, direct API - 3 keys to generate on a single page)
- Namecheap (direct API - reserved for accounts with 20+ domains OR 50 USD of balance/spend)
- Gandi (FR, simple API with a Personal Access Token)
- Porkbun (US, free and clean API - a favorite among indie hackers)
- Infomaniak (Switzerland - manual for the NS, but a clean API to automate other things afterward)
- IONOS (ex-1&1, EU - manual for the NS, IONOS API available for DNS records)
- Squarespace (ex-Google Domains - manual only, the Google API was killed in 2024)
- GoDaddy (manual only - their API does not allow automation for this)
- Other (OnlyDomains, NameSilo, Name.com, etc. - guided manual)
Store the answer in <registrar>. Move to Step 4.
Cloudflare is our DNS provider in every case (except when registrar=Cloudflare, where it is already covered by default).
Cloudflare architecture in hypervibe: all DNS operations go through the Cloudflare REST API via curl + the CLOUDFLARE_API_TOKEN token. No flarectl, no MCP.
_check-deps cloudflareresult=$(node "${CLAUDE_SKILL_DIR}/../../scripts/check-deps.mjs" cloudflare)
cf_ok=$(echo "$result" | node -e "console.log(JSON.parse(require('fs').readFileSync(0,'utf8')).cloudflare.ok)")
If cf_ok = true (CF token present AND valid against the API) -> move to Step 5. Nothing to configure.
If cf_ok = false -> route to /start so the user creates their Cloudflare token:
DNS will be managed by Cloudflare (free, fast, and essential for Email Routing). I need your Cloudflare account connected to your computer. That's not set up yet.
Run
/start- it creates a Cloudflare API token in 2 guided minutes, which will be used for all the plugin's Cloudflare operations (DNS, Workers, R2). Then you re-run/add-domainand I pick up here.
Once the user has run /start, re-invoke _check-deps cloudflare to confirm. Only continue to Step 5 if cf_ok = true.
ZONE_RESPONSE=$(curl -s -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
"https://api.cloudflare.com/client/v4/zones?name=<domain>")
ZONE_ID=$(echo "$ZONE_RESPONSE" | node -e "const d = JSON.parse(require('fs').readFileSync(0, 'utf8')); console.log(d.result?.[0]?.id || '');")
Retrieve the account_id then POST the zone:
ACCOUNT_ID=$(curl -s -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts" \
| node -e "const d = JSON.parse(require('fs').readFileSync(0, 'utf8')); console.log(d.result?.[0]?.id || '');")
CREATE_RESPONSE=$(curl -s -X POST -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"name\":\"<domain>\",\"account\":{\"id\":\"$ACCOUNT_ID\"},\"type\":\"full\"}" \
"https://api.cloudflare.com/client/v4/zones")
ZONE_ID=$(echo "$CREATE_RESPONSE" | node -e "const d = JSON.parse(require('fs').readFileSync(0, 'utf8')); console.log(d.result?.id || '');")
NS_INFO=$(curl -s -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
"https://api.cloudflare.com/client/v4/zones/$ZONE_ID")
NS1=$(echo "$NS_INFO" | node -e "const d = JSON.parse(require('fs').readFileSync(0, 'utf8')); console.log(d.result?.name_servers?.[0] || '');")
NS2=$(echo "$NS_INFO" | node -e "const d = JSON.parse(require('fs').readFileSync(0, 'utf8')); console.log(d.result?.name_servers?.[1] || '');")
Store ZONE_ID, NS1, NS2 for later.
Special case: if <registrar> == cloudflare AND the zone is managed by Cloudflare natively (Cloudflare Registrar) -> no need to change the NS, skip Step 6 and move to Step 7.
Branch to the sub-skill matching the registrar:
<registrar> | Sub-skill |
|---|---|
hostinger | _dns-hostinger |
cloudflare | _dns-cloudflare (skipped in practice, see Step 5) |
ovh | _dns-ovh |
namecheap | _dns-namecheap |
gandi | _dns-gandi |
porkbun | _dns-porkbun |
infomaniak | _dns-infomaniak-manual |
ionos | _dns-ionos-manual |
squarespace | _dns-squarespace-manual |
godaddy | _dns-godaddy-manual |
other | guide manually (see bottom of this section) |
Read and run the sub-skill's SKILL.md, passing it: <domain>, $NS1, $NS2. The sub-skill detects whether the registrar's MCP/CLI is connected -> if yes, pushes the NS directly; if not, guides the install + the auth + pushes. Always favor the auto-install of an MCP/CLI over the manual mode: it will be reused for other projects and for future changes.
other or explicit refusal of the auto-install)Go to your registrar's panel -> find the "Nameservers" or "DNS" section of your domain -> choose "Custom nameservers" -> replace with:
$NS1$NS2Save. Let me know when it's done.
Wait for confirmation.
Use the Cloudflare REST API via curl - ZONE_ID was stored in Step 5.
@ and www (if they exist)# List all the records in the zone
RECORDS=$(curl -s -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
"https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records?per_page=100")
# Extract the IDs of the records to delete (A on @ and CNAME on www.<domain>)
echo "$RECORDS" | node -e "
const d = JSON.parse(require('fs').readFileSync(0, 'utf8'));
const domain = '<domain>';
const toDelete = d.result.filter(r =>
(r.type === 'A' && (r.name === domain)) ||
(r.type === 'CNAME' && (r.name === 'www.' + domain))
);
for (const r of toDelete) console.log(r.id);
" | while read RID; do
curl -s -X DELETE -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
"https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$RID"
done
# A record: @ -> 76.76.21.21 (apex, proxy off)
curl -s -X POST -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"type\":\"A\",\"name\":\"<domain>\",\"content\":\"76.76.21.21\",\"ttl\":3600,\"proxied\":false}" \
"https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records"
# CNAME record: www -> cname.vercel-dns.com (proxy off)
curl -s -X POST -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"type\":\"CNAME\",\"name\":\"www\",\"content\":\"cname.vercel-dns.com\",\"ttl\":3600,\"proxied\":false}" \
"https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records"
Warning: \"proxied\":false is mandatory - Vercel manages its own HTTPS certificate and routing. The Cloudflare proxy interferes.
curl -s -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
"https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records?type=A&name=<domain>"
curl -s -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
"https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records?type=CNAME&name=www.<domain>"
✅ DNS configured! The records point to Vercel.
vercel domains add <domain>
If it does not work via the CLI:
Go to https://vercel.com -> your project -> Settings -> Domains -> Add Domain
Invoke _push-env-vars with:
NEXT_PUBLIC_APP_URL=https://<domain>The helper updates the local .env AND Vercel (production/preview/development) in a single operation, idempotently.
This step is critical for SEO. If *.vercel.app URLs remain in the code (sitemaps, metadata, JSON-LD, Open Graph), Google indexes the wrong URLs and the ranking is diluted.
Search for all occurrences of *.vercel.app in the source code:
grep -r "vercel\.app" <WEB_DIR>/src/ --include="*.ts" --include="*.tsx" -l
Replace each occurrence with either:
process.env.NEXT_PUBLIC_APP_URL ?? "http://localhost:3000" (for runtime code)https://<domain> (for static content such as legal pages)Files to check as a priority:
<WEB_DIR>/src/app/sitemap.ts - the baseUrl must use process.env.NEXT_PUBLIC_APP_URL<WEB_DIR>/src/app/layout.tsx or <WEB_DIR>/src/app/[locale]/layout.tsx - metadataBase, JSON-LD, Open Graph URLs<WEB_DIR>/src/app/robots.ts - the sitemap URLCheck that no occurrence remains:
grep -r "vercel\.app" <WEB_DIR>/src/ --include="*.ts" --include="*.tsx"
If the grep returns results, fix until it is empty.
Invoke _update-claude-md with:
custom:
## Custom domainThe production domain is `<domain>`.
Architecture: <Registrar> (registrar) -> Cloudflare (DNS) -> Vercel (hosting).
HTTPS certificate managed automatically by Vercel.
vercel domains inspect <domain>
If it does not work yet:
DNS propagation can take 5 to 30 minutes (rarely more, sometimes up to 24h). Try again in a few minutes.
⚠️ The audit of the integrations that depended on the old domain (OAuth, Stripe webhook, Analytics, etc.) is done in Step 11, after the Email Routing / Resend options - do not do it here, otherwise we ask the user to update Resend twice.
Offer:
Do you want to set up receiving emails on your domain (for example
contact@<domain>redirected to your real mailbox: Gmail, Outlook, etc.)?Shall I set that up?
If no -> move to Step 10.
If yes -> ask for the destination address:
Which email address do you want to redirect the messages to?
Then delegate to the /new-email-address skill: read and run new-email-address/SKILL.md, passing it the destination address as an argument. The skill handles enabling Email Routing on the zone (idempotent), asking the user for the desired prefix (contact, hello, support...), adding the destination address to Cloudflare, handling the email verification, and creating the redirection rule.
Case where delegation fails: if ZONE_ID or ACCOUNT_ID is not available in context at the moment of delegation, the sub-skill re-retrieves them via its Step 3 from <domain>. No variable to propagate explicitly other than the destination address.
Silently check whether Resend is configured: look for RESEND_API_KEY in the .env or resend in the dependencies.
If Resend is configured, offer:
Now that you have your own domain, I can configure Resend so that your outgoing emails are sent from
contact@<domain>instead of[email protected]. It's more professional and improves deliverability.Shall I set that up?
If yes -> read and run the _dns-resend/SKILL.md skill
If no -> skip
If Resend is not configured, offer nothing.
The domain change has probably desynchronized several integrations that still pointed to the previous Vercel URL:
POST to a dead URL)redirect_uri_mismatchThis step detects what is installed, automatically fixes what is CLI-able (Stripe, Resend already done in Step 10), and lists the remaining manual actions with auto-open of the dashboard. It is non-negotiable: a silently broken Stripe webhook is exactly the kind of bug that misses payments for days without anyone noticing.
Run this block once to detect what is in the project:
WEB="<WEB_DIR>"
ENV="$WEB/.env"
PKG="$WEB/package.json"
has_env() { grep -q "^$1=" "$ENV" 2>/dev/null && echo yes || echo no; }
has_real_env() {
# Skip placeholder / empty values (bootstrap injects placeholders)
local v
v=$(grep "^$1=" "$ENV" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '"')
[ -n "$v" ] && ! echo "$v" | grep -qiE "placeholder|todo|xxx|change.?me|your-" && echo yes || echo no
}
echo "STRIPE=$(has_real_env STRIPE_SECRET_KEY)"
echo "GOOGLE_OAUTH=$(has_real_env AUTH_GOOGLE_ID)"
echo "GITHUB_OAUTH=$(has_real_env AUTH_GITHUB_ID)"
echo "GA4=$(has_real_env NEXT_PUBLIC_GA_ID)"
echo "RESEND=$(has_real_env RESEND_API_KEY)"
echo "BREVO=$(has_real_env BREVO_API_KEY)"
Capture each KEY=yes/no line in memory. Skip the blocks below for the integrations that return no.
If STRIPE=yes, run the block below. It lists the webhook endpoints, identifies those whose URL contains vercel.app, and updates them to https://<domain>/api/webhooks/stripe (or whatever path is already configured on the webhook - we only replace the host).
NEW_DOMAIN="<domain>"
PAYLOAD=$(stripe webhook_endpoints list --limit 30 2>/dev/null) || { echo "STRIPE_FIX=cli_error"; exit 0; }
echo "$PAYLOAD" | node -e "
const data = JSON.parse(require('fs').readFileSync(0, 'utf8'));
const obsolete = (data.data || []).filter(w => /\.vercel\.app/.test(w.url));
if (obsolete.length === 0) { console.log('STRIPE_FIX=none'); process.exit(0); }
const NEW = process.env.NEW_DOMAIN;
for (const w of obsolete) {
const newUrl = w.url.replace(/https:\/\/[^/]*\.vercel\.app/, 'https://' + NEW);
console.log('PATCH=' + w.id + '|' + w.url + '|' + newUrl);
}
" | while IFS='|' read -r tag ID OLD NEW_URL; do
[ "$tag" = "PATCH" ] || continue
stripe webhook_endpoints update "$ID" --url "$NEW_URL" >/dev/null 2>&1 \
&& echo "STRIPE_FIX_OK=$ID|$OLD → $NEW_URL" \
|| echo "STRIPE_FIX_FAIL=$ID|$OLD"
done
Depending on the output:
STRIPE_FIX=none -> nothing to fix, no vercel.app webhook found. Silent skip.STRIPE_FIX_OK=... -> announce to the user: ✅ Stripe webhook updated: <old> → <new>. Test by re-listing: stripe webhook_endpoints retrieve <id> --output json | grep url.STRIPE_FIX_FAIL=... -> put it in the manual-actions stack (see 11.5) and log the cause (probably a restricted API key permission).STRIPE_FIX=cli_error -> the Stripe CLI is not authenticated. Re-trigger _setup-stripe-cli then retry./add-cron and the CF variant of /add-automation write the Vercel URL directly into the Worker's wrangler.toml ([vars] section). When the domain changes, the Worker keeps pinging the old URL -> cron silently broken.
Run the block below once. It scans all the repo's wrangler.toml files, patches the URLs that contain .vercel.app, and redeploys each affected Worker.
NEW_DOMAIN="<domain>"
# Search the whole project (not just WEB_DIR) - workers can live in apps/worker/
# in a monorepo or at the repo root in a single-project layout.
REPO_ROOT=$(cd "<WEB_DIR>" && git rev-parse --show-toplevel 2>/dev/null || echo "<WEB_DIR>")
WT_LIST=$(find "$REPO_ROOT" -name "wrangler.toml" -not -path "*/node_modules/*" -not -path "*/.next/*" 2>/dev/null)
if [ -z "$WT_LIST" ]; then
echo "CF_WORKERS_FIX=no_workers"
else
echo "$WT_LIST" | while read -r WT; do
if grep -qE "https?://[^\"']*\.vercel\.app" "$WT"; then
# Use a node script to do a safe replace (sed is fragile with TOML quoting).
OLD_CONTENT=$(cat "$WT")
NEW_CONTENT=$(node -e "
const s = require('fs').readFileSync('$WT','utf8');
const out = s.replace(/https?:\/\/[^\"'\\s]*\.vercel\.app/g, 'https://' + process.env.NEW_DOMAIN);
process.stdout.write(out);
")
if [ "$OLD_CONTENT" != "$NEW_CONTENT" ]; then
printf '%s' "$NEW_CONTENT" > "$WT"
echo "CF_WORKER_PATCHED=$WT"
# Redeploy from the worker's directory
WORKER_DIR=$(dirname "$WT")
(cd "$WORKER_DIR" && npx wrangler deploy 2>&1 | tail -3) \
&& echo "CF_WORKER_REDEPLOYED=$WT" \
|| echo "CF_WORKER_REDEPLOY_FAILED=$WT"
fi
fi
done
fi
Depending on the output:
CF_WORKERS_FIX=no_workers -> no Worker in the project, silent skip.CF_WORKER_PATCHED=<path> without CF_WORKER_REDEPLOYED -> the file is patched but the redeployment failed. Read the wrangler deploy output above to understand (auth? missing account_id?).CF_WORKER_REDEPLOYED=<path> -> ✅ announce to the user ✅ Cloudflare Worker <name> redeployed with the new URL.⚠️ Special case scripts/wrangler.toml of the shared db-backup: this Worker is outside the repo (~/.db-backup-worker/wrangler.toml) and does not contain a Vercel URL - it is not affected. The find above does not touch it (it only scans the project's repo).
/add-agent and the Render variant of /add-automation read the app's URL via an env var on Render (typically APP_URL or NEXT_PUBLIC_APP_URL). Render has no automatic link with Vercel: if NEXT_PUBLIC_APP_URL changes on the Vercel side, Render keeps the old value.
Run the block below if RENDER_API_KEY is available in the user's environment (otherwise skip silently - the user probably has no Render service).
NEW_DOMAIN="<domain>"
if [ -z "$RENDER_API_KEY" ]; then
echo "RENDER_FIX=no_api_key"
else
# 1. List all services on the user's Render account
SERVICES_JSON=$(curl -sS -H "Authorization: Bearer $RENDER_API_KEY" \
"https://api.render.com/v1/services?limit=50" 2>/dev/null)
if [ -z "$SERVICES_JSON" ] || echo "$SERVICES_JSON" | grep -q '"message"'; then
echo "RENDER_FIX=api_error"
else
SERVICE_IDS=$(echo "$SERVICES_JSON" | node -e "
const d = JSON.parse(require('fs').readFileSync(0,'utf8'));
(d || []).forEach(s => console.log((s.service||s).id));
")
if [ -z "$SERVICE_IDS" ]; then
echo "RENDER_FIX=no_services"
else
echo "$SERVICE_IDS" | while read -r SID; do
[ -z "$SID" ] && continue
# 2. Get env vars for this service
EV=$(curl -sS -H "Authorization: Bearer $RENDER_API_KEY" \
"https://api.render.com/v1/services/$SID/env-vars?limit=50" 2>/dev/null)
STALE=$(echo "$EV" | node -e "
const d = JSON.parse(require('fs').readFileSync(0,'utf8'));
(d || []).forEach(e => {
const ev = e.envVar || e;
if (ev.value && /https?:\/\/[^\s\"']*\.vercel\.app/.test(ev.value)) {
const nv = ev.value.replace(/https?:\/\/[^\s\"']*\.vercel\.app/g, 'https://' + process.env.NEW_DOMAIN);
console.log(ev.key + '|' + ev.value + '|' + nv);
}
});
")
[ -z "$STALE" ] && continue
# 3. Patch each stale env var via PUT
echo "$STALE" | while IFS='|' read -r KEY OLD_VAL NEW_VAL; do
curl -sS -X PUT -H "Authorization: Bearer $RENDER_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"value\":\"$NEW_VAL\"}" \
"https://api.render.com/v1/services/$SID/env-vars/$KEY" >/dev/null \
&& echo "RENDER_PATCHED=$SID:$KEY ($OLD_VAL → $NEW_VAL)" \
|| echo "RENDER_PATCH_FAILED=$SID:$KEY"
done
# 4. Trigger a redeploy so the new env vars actually take effect
curl -sS -X POST -H "Authorization: Bearer $RENDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"clearCache":"do_not_clear"}' \
"https://api.render.com/v1/services/$SID/deploys" >/dev/null \
&& echo "RENDER_REDEPLOYED=$SID" \
|| echo "RENDER_REDEPLOY_FAILED=$SID"
done
fi
fi
fi
Depending on the output:
RENDER_FIX=no_api_key or RENDER_FIX=no_services -> silent, the user has no Render service to fix.RENDER_FIX=api_error -> auth/network error, surface the message and switch to manual (Render dashboard link in 11.5).RENDER_PATCHED=<id>:<key> + RENDER_REDEPLOYED=<id> -> ✅ announce ✅ Render Service <id> updated (X env vars patched) + redeployment started.⚠️ The Render redeployment takes 2-5 min - no need to wait in this flow. Mention to the user that they can monitor it via https://dashboard.render.com.
For each detected integration that CANNOT be auto-fixed, run an identical structured mini-block. The pattern:
open-url.mjshttps://<domain>/... formatted)"it's done" from the userGOOGLE_OAUTH=yesnode "${CLAUDE_SKILL_DIR}/../../scripts/open-url.mjs" "https://console.cloud.google.com/apis/credentials"
Display:
⚠️ Google OAuth - manual action (no API)
I opened your Google Cloud console. Select your OAuth 2.0 Client ID (the one created by
/add-google-auth), then:
- Authorized JavaScript origins section -> + ADD URI button:
https://<domain>- Authorized redirect URIs section -> + ADD URI button:
https://<domain>/api/auth/callback/google- Do not delete the old
*.vercel.appURLs (they serve as a test fallback).- Click Save.
Tell me "it's done" when you're finished.
After confirmation: no probe possible (Google only fails at the moment of the user's click). Display:
ℹ️ If after saving you still see
redirect_uri_mismatch, that's Google's propagation (up to 5 min). Retry.
GITHUB_OAUTH=yesnode "${CLAUDE_SKILL_DIR}/../../scripts/open-url.mjs" "https://github.com/settings/developers"
Display:
⚠️ GitHub OAuth - manual action (no API)
I opened your GitHub OAuth Apps. Select the one created by
/add-github-auth, then:
- Homepage URL -> replace with
https://<domain>- Authorization callback URL -> replace with
https://<domain>/api/auth/callback/github- Click Update application.
Note: unlike Google, GitHub allows only one callback URL per app - no Vercel fallback possible. If you want to keep the Vercel URL for testing, create a second OAuth App.
Tell me "it's done".
GA4=yesnode "${CLAUDE_SKILL_DIR}/../../scripts/open-url.mjs" "https://analytics.google.com/"
Display:
⚠️ Google Analytics 4 - manual action
I opened GA. Go to:
- Admin (⚙️ bottom left) -> your property -> Data Streams
- Select the existing web stream
- Edit URL button -> set
https://<domain>(no trailing slash)- Save
Note: this does not break your existing data - GA4 does not filter by stream URL, it's just a label. But some hostname-based reports will be cleaner.
Tell me "it's done".
If the user uses /gsc, remind them separately:
ℹ️ Google Search Console - Search Console works by property, not by stream. The old
*.vercel.appproperty stays valid for its past data. For the new domain, run/gsc(or manually addhttps://<domain>on https://search.google.com/search-console). Not urgent, can be done later.
After all the auto-fixes and user confirmations, display a structured recap:
✅ Integrations audit complete
Automatically fixed:
- [✓ Stripe webhook
wh_xxx→ https://... - if applicable]- [✓ Cloudflare Worker
<name>redeployed with the new URL - if applicable]- [✓ Render Service
<id>: X env vars patched + redeployment started - if applicable]- [✓ Resend domain (see Step 10) if applicable]
Manual action confirmed by you:
- [✓ Google OAuth - redirect URI added]
- [✓ GA4 - stream URL updated]
- [etc.]
Not installed, so nothing to do:
- [GitHub OAuth, Stripe, etc. - based on what returns
noin 11.1]
If an auto-fix failed (STRIPE_FIX_FAIL) or if the user skipped a manual action, list it explicitly in a 4th section To do later with the dashboard URL and the values to paste.
If files were modified (replacement of vercel.app URLs, CLAUDE.md update, etc.):
git add -A && git commit -m "fix(seo): replace vercel.app URLs with custom domain <domain>"
git push
Summarize everything that was done:
✅ Domain connected! Your app is now accessible at https://
Architecture set up:
- (registrar) -> Cloudflare (DNS) -> Vercel (hosting)
If Email Routing was configured, add:
- Emails received at
contact@<domain>-> redirected to<address>
If Resend was configured, add:
- Emails sent from
contact@<domain>via Resend
The OAuth / Stripe / Analytics reminders are already covered by Step 11 - do not repeat them here.
Finish with:
The HTTPS certificate is managed automatically by Vercel.
npx claudepluginhub flavien-ia/hypervibe-harness --plugin hypervibeConfigures custom domains and TLS certificates on Render web services and static sites: DNS setup for CNAME, apex, and wildcard domains, verification, and troubleshooting.
Configures a custom domain in Brevo for professional email sending and adds DNS records via Cloudflare REST API. Internal skill called by /add-domain.
Guides DNS configuration for custom funnel domains on Netlify, Vercel, Cloudflare Pages. Covers A/CNAME records, SSL setup, provider references, verification commands, and troubleshooting.