Skip to main content

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 meansSource
NS points to an outside DNS host (GoDaddy, Namecheap, Porkbun, Cloudflare, etc.), Wix is only the target of the A recordThe 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 nslookup checks 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/TXT check 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 the A and CNAME records for web traffic — it must not add, remove, or edit any MX, SPF, DKIM, or other TXT record. A plain A/CNAME edit at any mainstream DNS host does not touch MX by itself, but always screenshot (or nslookup and 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_churches row is already built, reviewed, and the founder has said "go." Cutting DNS over to a half-finished or noindex'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).

  1. From /admin/[token] → Website → Domain, enter the bare domain (no https://, no trailing slash) and submit. This calls POST /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.
  2. Add those DNS records at the registrar (or inside Wix's own DNS manager for a Wix-purchased domain — §0): typically an A record @ → 76.76.21.21 for the apex, and a CNAME record www → cname.vercel-dns.com. for the www host (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).
  3. From the same dashboard screen, click "Verify" — calls POST /api/premium/domain/verify, which polls Vercel (checkDomainStatus()) and updates custom_domain_status to 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.
  4. 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 in middleware.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 A and www CNAME) in the same sitting — a half-done switch (apex moved, www still 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> showing SSL Certificate: Valid before 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 confirm www redirects 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: false when 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​

  1. 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.ts builds the host-aware sitemap served at <tenant-host>/sitemap.xml for ANY live custom-domain or *.john316.church Pro 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 published extra_pages will NOT have them in its sitemap until this is built. NEEDS-CODE if the tenant has extra pages that should be indexed: extend buildTenantSitemapUncached() to also enumerate a non-real-estate tenant's published, in-nav extra_pages (the same normalized list buildNavTree() in src/lib/pro-website-pages.ts already 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.
  2. 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).
  3. 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.
  4. 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 from middleware.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 in TENANT_LEGACY_CONFIG (keyed by vanity_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-us page. 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 /about is 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 to TenantLegacyConfig (same file), checked FIRST inside resolveLegacyRedirectTarget before 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 into middleware.ts for 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 owns middleware.ts for 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 and www (one should 308 to the other per §3).
  • Every published page (home + each extra_pages entry) 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[].email unset falls back to admin_email; since PR #1750, a submission with NEITHER resolved still saves and logs to ops_errors rather 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() in src/app/api/contact/church/route.ts, wired to a second resend.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, check chatbot_response_cache for this tenant and purge any row created before the change (chatbot_response_cache caches 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 a noindex'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.txt at 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:

  1. 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.
  2. Leave premium_churches.custom_domain_status and search_indexable as they are — no need to detach the domain from Vercel; it simply stops receiving traffic once DNS points elsewhere again.
  3. 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)​

  1. curl -I https://<domain>/ → 200, valid cert, no noindex (once flipped).
  2. curl -I https://www.<domain>/ → 308 to apex (or confirms the chosen convention).
  3. curl https://<domain>/sitemap.xml → lists every real page, not just /.
  4. curl https://<domain>/robots.txt → serves the new site's policy, not a cached Wix one.
  5. Submit the contact form → notification arrives at the real address within a few minutes; submitter confirmation arrives too.
  6. Ask the chat widget 2 real FAQ questions → correct, current answers.
  7. Load 2–3 old Wix URL shapes → either 308s correctly or is a known, accepted gap.
  8. 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.md
  • churchwiseai-web origin/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