Knowledge > Runbooks > Deployment > Pro Website Wix Cutover
Pro Website — Cutting a Domain Over from Wix
Generic, any-tenant runbook for the day a customer's real domain (previously live on
Wix) starts pointing at their ChurchWiseAI Pro Website instead. First customer to use
it: Almost Home Ministries, almosthomeministries.org — but nothing here is Almost
Home-specific; tenant facts belong in that tenant's own go-live checklist (see e.g.
ai-company-os/tasks/artifacts/TASK-20260928-almost-home/GO-LIVE-CHECKLIST.md).
Do not skip §1 (pre-flight). The single biggest way this goes wrong is discovering,
mid-cutover, that nobody at ChurchWiseAI has the registrar/Wix login — DNS changes are
the customer's own manual step (domain-setup.md), and Wix does not support delegating
that to a third party.
0. Find out who actually hosts DNS FIRST — don't assume it's Wix
A site being built on Wix does not mean Wix hosts its DNS. The domain may be
registered at GoDaddy, Namecheap, Porkbun, etc., with DNS still managed there and only
an A record pointed AT Wix's IP — in which case Wix never enters the cutover at all;
the DNS host does. Check this BEFORE asking anyone for a "Wix login":
nslookup -type=NS <domain> # who resolves it — Wix nameservers, or an outside DNS host?
nslookup -type=A <domain> # current apex target (Wix's is commonly 185.230.63.x range)
nslookup -type=MX <domain> # where mail actually goes — NOT necessarily the same host as NS
nslookup -type=TXT <domain> # SPF and any provider-verification TXT records (e.g. Microsoft/Google)
ns*.domaincontrol.com = GoDaddy DNS. Other common outside hosts: Cloudflare, Namecheap
(*.registrar-servers.com), Porkbun. If the NS records point to one of these instead of
Wix, the login needed is that DNS provider's, not Wix's — and the cutover is smaller
than it looks: only the A/CNAME records controlling web traffic need to change,
nothing else on that DNS host is touched.
Situation (confirmed by the nslookup above) | What it means | Source |
|---|---|---|
NS points to an outside DNS host (GoDaddy, Namecheap, Porkbun, Cloudflare, etc.), Wix is only the target of the A record | The registrar/DNS-host login is what's needed — NOT a Wix login. Whoever holds it edits the A/CNAME records directly, at that provider, same mechanism as pointing them at Wix in the first place, just with Vercel's values instead. Everything else at that DNS host (MX, SPF, DKIM, other TXT records) is untouched by this edit — confirm by reading the full record list before and after, but a plain A/CNAME change does not, by itself, touch MX. Connecting a Domain Purchased Elsewhere to Wix | |
| NS points to Wix's own nameservers (domain purchased through Wix, DNS also hosted there) | Wix IS the DNS host. Its nameservers cannot be changed by an outside party. To point it elsewhere, edit the domain's DNS records inside the Wix account's own DNS manager — Wix explicitly supports this ("Connecting a Wix Domain to an External Site": "You can connect a domain purchased from Wix to any site, even if it's hosted on another platform... edit the DNS records in your Wix account so they point to your external site"). If Wix's pointing method can't satisfy the external host's requirements (e.g. it needs full nameserver delegation), the domain must be transferred away from Wix first. Connecting a Wix Domain to an External Site |
Either way, DNS is a manual, customer-side (or founder-side, if we hold the login)
step — no ChurchWiseAI code automates a third party's DNS (confirmed: no Wix-specific
or registrar-specific automation exists anywhere in churchwiseai-web; codemap.md §7
for the Almost Home build independently reached the same conclusion — "a process gap,
not a code gap").
Wix keeps serving the site the whole time this is undecided. Nothing breaks, and there is no rush, until the DNS records actually change at the registrar (or inside Wix's own DNS manager, for a Wix-purchased domain) — the old site keeps answering at its current A/CNAME values until that edit happens and propagates.
1. Pre-flight (before touching any DNS)
- Run §0's
nslookupchecks and identify the actual DNS host — do this before asking anyone for a login, since the answer determines WHICH login to ask for (a Wix-purchased domain needs the Wix login; a domain merely pointed at Wix from GoDaddy/ Namecheap/Porkbun/Cloudflare needs THAT provider's login instead). - Who holds that DNS host's login? If it's the customer, get them to either (a) make the DNS change themselves from this runbook's §3 values, live on a screen-share, or (b) hand ChurchWiseAI temporary access for this one change. Do not proceed to §3 without an answer — this is the #1 way a cutover stalls mid-flight.
- Export anything worth keeping from Wix before the domain moves — once DNS
points elsewhere, the Wix site becomes hard for the customer to find their way back to
(see §7, "keep Wix up 30 days," for why the SITE itself should stay live regardless):
- Blog posts — Wix Blog Manager → Export → downloads an XML file. This is the only whole-content export Wix offers.
- CMS collections / products (if any) — CMS → Collections → ⋯ → Export to CSV, per collection.
- Form submissions — CANNOT be exported. Wix has no bulk-export for form responses. Manually copy anything live-critical (a waitlist, a signed-up-for-updates list, a pending order) out of the Wix dashboard before cutover, or accept it stays stranded in a site nobody will check again after go-live.
- Images/photos — download originals from the Wix Media Manager; do not rely on screenshotting the live pages.
- Wix's own position: "Wix is a closed platform... site code, design, layouts, forms, products, and member data cannot be exported." Plan the export list assuming nothing else comes with you.
- Email — the §0
nslookup -type=MX/TXTcheck already told you where mail goes; confirm it before touching anything. MX does not have to match NS/A — a domain can have its DNS at GoDaddy, its web traffic pointed at Wix, and its mail running through Microsoft 365 or Google Workspace (GoDaddy resells both; the MX target and an SPF/TXT record naming the tenant, e.g.NETORGFT...onmicrosoft.com, confirm which). The DNS change in §3 touches ONLY theAandCNAMErecords for web traffic — it must not add, remove, or edit any MX, SPF, DKIM, or other TXT record. A plainA/CNAMEedit at any mainstream DNS host does not touch MX by itself, but always screenshot (ornslookupand save) the full record set BEFORE editing anything, so there's a known-good state to diff against and restore from if mail breaks. - Confirm the Pro Website side is actually ready to receive traffic — this
runbook assumes the tenant's
premium_churchesrow is already built, reviewed, and the founder has said "go." Cutting DNS over to a half-finished ornoindex'd site with broken contact routing is worse than leaving Wix up.
2. The ChurchWiseAI side — attach the domain
Two mechanisms exist in the codebase (codemap.md §7, verified against origin/main
2026-09-29):
A) Self-service, from the tenant's own admin dashboard (preferred — use this first)
Requires the tenant's plan to include custom_domain (canAccess(plan, 'custom_domain')
— true for cwa_pro_website bundled, and for cwa_website/cwa_complete (+_annual)
under three-plan pricing; no setup fee on any three-plan-pricing key —
src/app/api/premium/domain/add/route.ts).
- From
/admin/[token]→ Website → Domain, enter the bare domain (nohttps://, no trailing slash) and submit. This callsPOST /api/premium/domain/add, which:- Registers the domain with Vercel (
addDomainToProject()). - Writes
premium_churches.custom_domain,custom_domain_status = 'pending_dns',custom_domain_added_at. - Returns the exact DNS records to add (
cname_target,verification_records) — the UI renders these; don't hand-type generic values, use what this call returns. - Fails closed: if the Stripe setup-fee write or the DB write fails partway, it rolls back the Vercel registration rather than leaving orphan state.
- Registers the domain with Vercel (
- Add those DNS records at the registrar (or inside Wix's own DNS manager for a
Wix-purchased domain — §0): typically an
Arecord@ → 76.76.21.21for the apex, and aCNAMErecordwww → cname.vercel-dns.com.for thewwwhost (Vercel's standard values — confirm against what step 1 actually returned for this domain, since an apex-only or subdomain-only input changes which record type applies). - From the same dashboard screen, click "Verify" — calls
POST /api/premium/domain/verify, which polls Vercel (checkDomainStatus()) and updatescustom_domain_statusto whatever Vercel reports (pending_dns→verifying→live, or an error state). This route is POST-only by design — GET is blocked (405) because it mutates state and a prefetcher/scanner firing GET must never silently advance the domain's status. custom_domain_status === 'live'is the load-bearing gate —middleware.ts's custom-domain rewrite to/s/[slug]only fires once status is'live'(codemap.md §7; confirmed inmiddleware.ts's "Customer custom domains" block). A domain that is merely added is not yet routed — repeat step 3 until it flips.
B) Manual founder step (only if A doesn't apply — e.g. a brand-new domain never
attached to this Vercel project before, or a {slug}.john316.church subdomain)
Follow runbooks/deployment/domain-setup.md directly: add the DNS record at
Porkbun/registrar → vercel domains add <domain> --project churchwiseai-web →
vercel domains inspect <domain> to confirm SSL is Valid → curl-test → confirm in a
browser. Only needed if the self-service flow in (A) isn't available for this tenant's
plan or domain shape.
3. The switch — timing, TTL, both hosts, SSL
- Do the DNS edit once, for both records (apex
AandwwwCNAME) in the same sitting — a half-done switch (apex moved,wwwstill on Wix, or vice versa) means half of incoming traffic/links hit the old site and half hit the new one, with no way to predict which a given visitor gets. - Propagation: the portfolio's own domain runbook says 1–60 minutes typically, up to 48 hours worst case. Wix's own documentation is blunter — "DNS changes... can take up to 48 hours to fully update across the internet." Plan the go-live window and any founder/customer announcement around the 48-hour figure, not the optimistic one.
- SSL: Vercel auto-provisions a certificate once DNS resolves to it — confirm with
vercel domains inspect <domain>showingSSL Certificate: Validbefore telling anyone the site is live. Do not skip this even if the page loads over HTTP-then-redirect in a browser that already has a cached connection. - www vs. apex convention: this portfolio's Pro Website middleware canonicalizes
www.<customdomain>→ the bare apex with a 308, but only when the apex itself already resolves to a live tenant — so get the apex live and verified FIRST, then confirmwwwredirects to it, not the reverse.
4. The noindex flip
Every pre-launch Pro Website tenant is provisioned cap_info.search_indexable = "false"
(a string, not a boolean — verified against the live schema) as part of the standard
proposal-protection pattern (codemap.md §6). This drives:
src/app/s/[slug]/page.tsx— home-page<meta name="robots">(index: false, follow: falsewhen the flag is"false").src/app/s/[slug]/[page]/page.tsx— same flag, same effect, on every extra page.
Flipping it is a one-value DB change, no deploy (by design — see the code's own comment: "Lifting it at go-live is a one-value DB change, no deploy"):
update premium_churches
set cap_info = jsonb_set(cap_info, '{search_indexable}', '"true"')
where id = '<premium_churches.id>';
Do this only after DNS has switched and the founder has confirmed the site is ready
for the public — not before, and not automatically on DNS propagation (the two are
deliberately decoupled: a site can go DNS-live on its new domain while staying noindex
for a soft-launch window if the founder wants one).
5. Sitemap + Google Search Console
- Verify the sitemap actually lists every real page before submitting it — don't
assume. As of 2026-09-29,
src/lib/real-estate/tenant-sitemap.tsbuilds the host-aware sitemap served at<tenant-host>/sitemap.xmlfor ANY live custom-domain or*.john316.churchPro Website. It gives every non-real-estate vertical (church, ministry, funeral, vet, local-business) only the home-page URL —if (!match.isRealEstate) return entries;(line ~97) is an explicit, documented design floor, not a bug specific to one tenant: "a full per-page manifest for those verticals' owner-authored pages is a separate, larger project." A tenant with publishedextra_pageswill NOT have them in its sitemap until this is built. NEEDS-CODE if the tenant has extra pages that should be indexed: extendbuildTenantSitemapUncached()to also enumerate a non-real-estate tenant's published, in-navextra_pages(the same normalized listbuildNavTree()insrc/lib/pro-website-pages.tsalready produces for the nav) as additional sitemap entries, gated the same way real estate's branch is. This is a small, generic, template-feature fix — not a per-tenant content edit — and should land once, for every future non-real-estate Pro Website launch, not be re-discovered each time. - Add/verify the domain as a property in Google Search Console (use the domain
property type, not URL-prefix, so it covers both apex and
www). - Submit the sitemap (
https://<domain>/sitemap.xml) from GSC once step 1 is confirmed complete for this tenant, and re-submit if the sitemap gap above gets fixed after an initial submission with only the homepage. - Request indexing on the homepage directly from GSC's URL Inspection tool rather than waiting for the crawl queue, especially inside the 48-hour DNS-propagation window.
6. Redirect map — old Wix URLs → new Pro Website paths
No generic mechanism exists today for "old path X on this tenant → new path Y." What
DOES exist, verified against origin/main 2026-09-29:
src/lib/real-estate/legacy-redirects.ts(resolveLegacyRedirectTarget, called frommiddleware.ts's "Customer custom domains" block for ANY live custom-domain tenant, any vertical) maps old-vendor URL shapes to new paths on the same host with a 308. But every pattern in it (SIMPLE_PATH_MAP,NUMERIC_LISTING_RE,AREAS_RE,OLD_BLOG_POST_RE,OLD_ACCOUNT_UTILITY_RE) is hardcoded to the one old real-estate vendor (SellingToolz) this was built for, and a tenant only gets ANY of it if it has an entry inTENANT_LEGACY_CONFIG(keyed byvanity_slug) — opt-in per tenant, never a cross-tenant default, after a 2026-09-22 incident where a shared pattern table wrongly 308'd a church's real/about-uspage. There is no equivalent table for Wix-shaped old URLs, and Wix's own URL shapes (/store,/testimonials,/about,/donate, etc.) are generic enough that reusing the real-estate module conceptually doesn't fit — a church or ministry's/aboutis very likely a REAL page on the new site, not a dead shape to intercept.- NEEDS-CODE — the minimal fix: add a plain
pathMap?: Record<string, string>field toTenantLegacyConfig(same file), checked FIRST insideresolveLegacyRedirectTargetbefore any of the real-estate-specific regex tables, keyed by exact old pathname (e.g.{'/store': '/plaques', '/testimonials': '/testimonies', '/donate': '/ways-to-help'}). This reuses the exact call site already wired intomiddleware.tsfor every custom-domain tenant — no new middleware block, no new DB column, just a small, vertical-neutral extension to an existing per-tenant config object. Until this ships, a tenant's old Wix paths 404 on the new domain unless they happen to share the exact same path on the new site. - In the meantime: build the tenant's own
<old-path> → <new-path>table as part of its go-live checklist (see the tenant-specific checklist for an example), and either (a) wait for the code fix above, or (b) if the gap must close sooner, ask whoever ownsmiddleware.tsfor a same-day scoped fix rather than shipping the cutover with known dead links from every external backlink, bookmark, and search result pointing at the old Wix path shapes.
7. Keep Wix up for 30 days as a fallback
Do not cancel or downgrade the Wix subscription, and do not delete the Wix site, for at least 30 days after DNS cuts over. Reasons:
- DNS propagation is not instant or universal — some resolvers (especially ISP-level caches with a long TTL from before this cutover) can keep serving the OLD A/CNAME values for days. If Wix is cancelled the moment DNS changes, any visitor still hitting a stale resolver sees an error page instead of the old (still-correct) site.
- It is the only rollback path (§9) if something on the new site breaks post-launch in a way that isn't safe to leave live even briefly.
- It preserves the ability to go back into the Wix dashboard for a forgotten export (form submissions notwithstanding — see §1) during the window when the customer is most likely to notice something was missed.
Set a calendar reminder for the 30-day mark to actually cancel Wix, rather than letting it silently auto-renew indefinitely.
8. Verification checklist — on the REAL host, after cutover
Per this portfolio's verification ladder (CLAUDE.md), a Playwright build pass or a curl
against the staging *.john316.church host does NOT satisfy this — everything below
must be checked on the tenant's real, now-live domain, post-DNS-switch, post-SSL:
- Home page loads over
https://, no certificate warning, both apex andwww(one should 308 to the other per §3). - Every published page (home + each
extra_pagesentry) returns 200 and renders its real content, not a 404 or the platform's generic not-found. - Contact form(s) — submit at least one real test through each configured route
(Volunteer, Prayer Request, General, etc. — whatever this tenant has) and confirm the
notification email arrives at the REAL inbox that route is supposed to reach, not a
staging placeholder or a fallback (
contact_routing[].emailunset falls back toadmin_email; since PR #1750, a submission with NEITHER resolved still saves and logs toops_errorsrather than vanishing — but "didn't get lost" is not the same as "reached the right person," which is what this check is actually for). - Submitter confirmation email — confirm the person who submitted the form
actually receives a confirmation (this now exists in code —
buildConfirmationEmail()insrc/app/api/contact/church/route.ts, wired to a secondresend.emails.send()call — verify it fires for THIS tenant's real domain, not just that the function exists). - Chat widget — open it on the live domain (not staging) and ask 2–3 of the
tenant's own real FAQ questions; confirm answers are current (see the note on
chatbot_response_cache's 7-day TTL below) and that any lead-capture tool (capture_visitor_contact/request_callback) actually notifies someone. - Semantic cache staleness — if any content (FAQ knowledge,
custom_staff, contact routing) changed AFTER the domain went live, checkchatbot_response_cachefor this tenant and purge any row created before the change (chatbot_response_cachecaches per-query-embedding for 7 days regardless of underlying data changes — confirmed no automatic invalidation exists). - JSON-LD / structured data renders on the real host (view source, not staging) —
especially any
sameAs/taxID/address fields that differ between anoindex'd staging preview and the real indexable page. - robots.txt on the real host allows the crawlers this tenant wants (compare
against staging — the per-crawler allow-list is generated the same way, but confirm it
actually serves from the new domain, not a cached Wix
robots.txtat the CDN edge). - sitemap.xml on the real host lists what §5 says it should — re-check after any fix to the extra-pages gap.
- Old Wix URL shapes (§6) either redirect correctly or are confirmed acceptable as a known, temporary gap — don't discover 404s from a customer's own bookmark.
- Google Business Profile "Website" field updated to the new domain (if this tenant has a GBP listing) — a stale GBP link sends searchers back to Wix even after cutover.
- Any inbound email test — send a real email to the domain's primary mailbox address and confirm it's received exactly as before (§1's MX pre-flight check, verified in practice, not just by reading the DNS records).
9. Rollback
If anything above fails in a way that can't be fixed same-day:
- Revert the DNS records at the registrar (or inside Wix's DNS manager) back to Wix's original values — Wix is still live (§7), so this restores the old site with no data loss on the Wix side.
- Leave
premium_churches.custom_domain_statusandsearch_indexableas they are — no need to detach the domain from Vercel; it simply stops receiving traffic once DNS points elsewhere again. - Note the rollback and the reason in
DECISION_LOG.md, and re-run this runbook from §1 once the blocking issue is fixed.
Smoke list (short form, for a second pass or a different agent to run quickly)
curl -I https://<domain>/→200, valid cert, nonoindex(once flipped).curl -I https://www.<domain>/→308to apex (or confirms the chosen convention).curl https://<domain>/sitemap.xml→ lists every real page, not just/.curl https://<domain>/robots.txt→ serves the new site's policy, not a cached Wix one.- Submit the contact form → notification arrives at the real address within a few minutes; submitter confirmation arrives too.
- Ask the chat widget 2 real FAQ questions → correct, current answers.
- Load 2–3 old Wix URL shapes → either 308s correctly or is a known, accepted gap.
- Send a real email to the domain's main mailbox → arrives.
Sources
ai-company-os/tasks/artifacts/TASK-20260928-almost-home/codemap.md§6, §7 (first real-world walk of this cutover path, Almost Home Ministries, 2026-09-28)ai-company-os/tasks/artifacts/TASK-20260928-almost-home/geo-and-traffic-2026-09-29.md(sitemap gap, finding #1)runbooks/deployment/domain-setup.mdchurchwiseai-weborigin/main, read 2026-09-29:src/app/api/premium/domain/{add,verify}/route.ts,src/middleware.ts,src/lib/real-estate/{legacy-redirects,tenant-sitemap}.ts,src/app/s/[slug]/page.tsx,src/app/s/[slug]/[page]/page.tsx,src/app/api/contact/church/route.ts,src/lib/semantic-cache.ts- Wix Help Center: Connecting a Domain Purchased Elsewhere to Wix, Connecting a Wix Domain to an External Site, Transferring vs. Connecting Your Domain to Wix