I recently migrated geo-before.sourcedcode.com, a Next.js app on Vercel with no CMS at all, to AEM Edge Delivery Services (EDS) with content authored in da.live. On the old site, every word of copy lived in the code, so every text change meant a developer and a deploy. The new site, geo.sourcedcode.com, looks the same, keeps the same URLs, and every page can now be edited in da.live by an author.
I did the whole migration with Claude Code and Adobe’s stardust plugin, which recreates a site near pixel-perfect and proves it with measured gates against the live page. I used a personal Claude Pro account (about $30 a month). That plan has a usage limit that resets every 5 hours, and it shaped how long this took, which I cover at the end.
This post is every step I followed, from a blank Mac to the live site. All the official examples and docs are at aem.live.
Note!
You can quite literally follow my step by step guide below to migrate https://geo-before.sourcedcode.com to https://geo.sourcedcode.com within 0-8 hour window.
- Source site (link public): https://geo-before.sourcedcode.com (Next.js, Vercel, no CMS, 16 URLs in its sitemap)
- GitHub repo (link private): sourcedcodecom/geo-sourcedcode-com
- da.live (link private): https://da.live/#/sourcedcodecom/geo-sourcedcode-com
- Preview (link private): https://main–geo-sourcedcode-com–sourcedcodecom.aem.page/
- Live origin (link private): https://main–geo-sourcedcode-com–sourcedcodecom.aem.live/
- Production (link public): https://geo.sourcedcode.com (DNS on Cloudflare)
The plan is simple: set everything up, migrate one page and check it, then migrate every other page in parallel, then put the site behind Cloudflare.
- Part 1: Install the Tools
- Part 2: Set Up Git and GitHub
- Part 3: Create the EDS Project
- Part 4: Get the Code on Your Computer
- Part 5: Push Code (You Will Do This Every Time)
- Part 6: Connect da.live
- Part 7: Add Stardust
- Part 8: Goal 1, Migrate One Page as a Pilot
- Part 9: Check the Pilot
- Part 10: Goal 2, Migrate Every Other Page in Parallel
- Part 11: Check the Whole Site
- Part 12: Put It on geo.sourcedcode.com (Cloudflare)
- Part 13: Verify and Roll Back
- Part 14: Record It
- What Actually Happened on This First Migration
Setting Up
Part 1: Install the Tools
1. Open Terminal.
2. Install Homebrew:
|
1
|
/bin/bash -c “$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)”
|
3. Install Git and the GitHub CLI:
|
1
|
brew install git gh
|
4. Install nvm, then add the two lines it prints to ~/.zshrc and reopen Terminal:
|
1
|
brew install nvm
|
5. Install Node 22 and make it the default. Stardust needs Node 22 or newer, and new shells (Claude’s included) use the default:
|
1
|
nvm install 22 && nvm alias default 22 && node -v
|
6. Install the AEM CLI:
|
1
|
npm install -g @adobe/aem-cli
|
7. Install playwright-cli:
|
1
|
npm install -g @playwright/cli
|
8. Install Google Chrome and the AEM Sidekick extension, then pin the extension.
Part 2: Set Up Git and GitHub
9. Set your name and email:
|
1
2 |
git config –global user.name “Your Name”
git config –global user.email “you@example.com” |
10. Log in to GitHub. Choose GitHub.com → HTTPS → Yes → Login with a web browser:
|
1
|
gh auth login
|
Part 3: Create the EDS Project
11. Open https://github.com/adobe/aem-boilerplate and click Use this template → Create a new repository.
12. Set the owner to sourcedcodecom and the name to geo-sourcedcode-com, make it Public, and click Create repository.
13. Go to https://github.com/apps/aem-code-sync/installations/new, choose sourcedcodecom → Only select repositories → geo-sourcedcode-com, and click Save.
14. On the AEM setup page, keep the content source as Document Authoring (da.live) and click Finish setup.
15. Open the preview and live URLs from the table above. Both should load.
Part 4: Get the Code on Your Computer
16. Clone the repo:
|
1
|
mkdir -p ~/git/2026 && cd ~/git/2026 && gh repo clone sourcedcodecom/geo-sourcedcode-com && cd geo-sourcedcode-com
|
17. Install dependencies. Don’t run npm init, because the template already has a package.json:
|
1
|
npm install
|
18. Pin Node 22 for the project:
|
1
|
echo 22 > .nvmrc
|
19. Start the local server. It opens http://localhost:3000/; press Ctrl+C to stop it:
|
1
|
aem up
|
Part 5: Push Code (You Will Do This Every Time)
20. Commit and push:
|
1
|
git add .nvmrc && git commit -m “Pin Node 22” && git push
|
21. Wait about 30 seconds and reload the preview URL. Code Sync has deployed your push.
Part 6: Connect da.live
22. Sign in at https://da.live with your Adobe ID.
23. Open https://da.live/#/sourcedcodecom/geo-sourcedcode-com. You should see index, nav and footer.
24. Edit index, click Preview and then Publish, and reload the live URL. You have now proven the loop: da.live content plus GitHub code ends up on aem.live.
Part 7: Add Stardust
25. Create .claude/settings.json:
|
1
2 3 4 5 6 7 8 9 10 11 |
{
“enabledPlugins”: { “stardust@adobe-skills”: true, “aem-edge-delivery-services@adobe-skills”: true, “impeccable@impeccable”: true }, “extraKnownMarketplaces”: { “adobe-skills”: { “source”: { “source”: “github”, “repo”: “adobe/skills” } }, “impeccable”: { “source”: { “source”: “github”, “repo”: “pbakaus/impeccable” } } } } |
26. Push it:
|
1
|
git add .claude/settings.json && git commit -m “Add stardust plugins” && git push
|
27. In the Claude desktop app, open a new Code session on ~/git/2026/geo-sourcedcode-com. Trust the folder and turn on auto mode.
28. Ask list the stardust skills you have. If none show up, run /reload-plugins.
Migrating the Site
Part 8: Goal 1, Migrate One Page as a Pilot
29. Capture the page. When asked, choose replica (keep the design):
|
1
|
/stardust:extract https://geo-before.sourcedcode.com/geo-vs-aio –single –dynamics
|
30. Run the pilot goal. A /goal keeps Claude working until every condition in it is proven in the conversation:
|
1
|
/goal Run /stardust:replica on https://geo-before.sourcedcode.com/geo-vs-aio as a single-page pilot (–first-page https://geo-before.sourcedcode.com/geo-vs-aio), then deploy it to da.live (org sourcedcodecom, site geo-sourcedcode-com) with /stardust:deploy. The production host will be https://geo.sourcedcode.com, so canonicals and og:url must use that host, not geo-before. Done when this conversation shows: (1) curl -s -o /dev/null -w “%{http_code}” https://main–geo-sourcedcode-com–sourcedcodecom.aem.live/geo-vs-aio prints 200; (2) the stardust gate at 1440 and 360 with 0 looks-same red and 0 nothing-lost yellow; (3) the comparison table is a real authorable block; (4) the editability gate passes; (5) the title, description and the 4 JSON-LD graphs (WebPage, BreadcrumbList, Organization, WebSite) match the source page, with the production host in canonical and og:url; (6) all code committed and pushed. Also counts as met: you stopped and asked me something.
|
31. Sign in to Adobe when Chrome opens. The token lasts 24 hours, but if you are following my guide to migrate geo-before.sourcedcode.com, it should take 1 hour or less.
Part 9: Check the Pilot
32. Compare https://main–geo-sourcedcode-com–sourcedcodecom.aem.live/geo-vs-aio(this would be your own URL, this example is mine) with the source page at laptop width and phone width.
33. Edit a sentence in da.live, then Preview → Publish. Your edit should appear.
34. Don’t start Goal 2 until this page is right. Every landing page will be built from it.
Part 10: Goal 2, Migrate Every Other Page in Parallel
Why parallel matters on a Pro plan. Two things eat the 5-hour allowance:
- The model. Opus uses the allowance faster than Sonnet.
- The size of the main conversation. Every step re-reads the whole conversation, so a long one costs more per step.
The cheapest way to work is a main session that only coordinates, with small parallel agents doing the building. Each agent starts with a small context, finishes, and hands back a short report.
Before you paste the goal:
35. Run /compact so the main conversation starts Goal 2 small. The pilot’s results are saved on disk in stardust/, so nothing is lost.
36. Note when your 5-hour window resets. If you hit the limit, the work stops; after the reset, type continue and it picks up from the files on disk.
37. Paste:
|
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
/goal Migrate every URL in https://geo-before.sourcedcode.com/sitemap.xml (a Next.js app on Vercel with no CMS; all copy is in its rendered HTML) to AEM Edge Delivery Services with da.live content (org sourcedcodecom, site geo-sourcedcode-com), keeping the current design with stardust replica. The production host is https://geo.sourcedcode.com: canonicals, og:url, JSON-LD @id and url values, and the sitemap must use it, never geo-before. /geo-vs-aio is already migrated and approved: it is the archetype for the landing pages, and its blocks, header, footer and styles are reused, not rebuilt.
I am on a Claude Pro plan with a 5-hour usage limit, so keep the main thread lean and push the work into parallel subagents: Wave 0 (main thread, short): stardust extract –prep over the whole sitemap so stardust/state.json lists every URL with a page type. Write stardust/rollout/brief.md: which paths each stream owns, that streams never edit shared files (styles/, scripts/, head.html, blocks/header, blocks/footer, or any block another stream uses) and send the main thread a change request with measured numbers instead, that streams git pull –rebase before every push, and the gate each page must pass. Ask me now, in one message, about every config change Waves 1 to 3 will need (query index, sitemap, robots), so nothing waits on me later. Wave 1 (3 subagents at once): H builds, deploys and gates the home page (/); L builds the insights listing (/insights) as a block generated from a query index, so new articles appear automatically; A builds the insights article template on /insights/what-is-generative-engine-optimization, plus a reusable importer that turns any source article into da.live content. Wait for all three to pass, then merge. Wave 2 (4 subagents at once): P migrates the remaining landing pages (services, ai-visibility-assessment, about, contact) using the /geo-vs-aio blocks; A1, A2 and A3 each migrate a third of the remaining insights articles with the Wave 1 template and importer. Each stream deploys through the DA Source API, gates its own pages at 1440 and 360, and pushes its own code. Then merge and publish the index. Wave 3 (verification, split across 3 subagents at once, one third of the pages each): the full gate sweep, editability and metadata checks. The main thread then runs Lighthouse on one page per type, adds /sitemap.xml, /robots.txt and /llms.txt, checks internal links, and runs stardust qa. Re-gate only the pages that fail. Stop and ask me before any site or da.live config change, anything in DNS or Cloudflare, or deleting anything. Done when this conversation shows all of: (1) every sitemap URL returns 200 on https://main–geo-sourcedcode-com–sourcedcodecom.aem.live at the same path; (2) a stardust gate sweep over all pages at 1440 and 360 with 0 looks-same red and 0 nothing-lost yellow; (3) mobile Lighthouse performance 100 on every page type; (4) the da.live editability gate with 0 dead and 0 duplicated on every page; (5) titles, descriptions, Open Graph tags and every JSON-LD graph matching the source, with geo.sourcedcode.com as the host; (6) /sitemap.xml, /robots.txt and /llms.txt on aem.live; (7) a clean stardust qa sweep with no broken internal links; (8) all code committed and pushed, all content published, and every worktree cleaned up. Also counts as met: you stopped at a stop point and asked me. |
38. While it runs:
- Answer the config question in Wave 0 straight away.
- If you hit the usage limit, wait for the reset and type continue.
- Don’t open a second Claude session on the same repo.
Part 11: Check the Whole Site
39. Click through home, /insights, a few articles and every landing page on the live origin.
40. Edit one article in da.live and publish it. The change should show on the article and on /insights.
41. Check that every URL answers 200:
|
1
|
for u in $(curl -s https://geo-before.sourcedcode.com/sitemap.xml | grep -o ‘<loc>[^<]*’ | sed ‘s#<loc>https://geo-before.sourcedcode.com##’); do echo “$(curl -s -o /dev/null -w ‘%{http_code}’ “https://main–geo-sourcedcode-com–sourcedcodecom.aem.live${u:-/}”) ${u:-/}”; done
|
Going Live
Part 12: Put It on geo.sourcedcode.com (Cloudflare)
Create a worker just for this site. Each worker sends traffic to exactly one aem.live site, so make a new worker. Don’t edit one that already serves another domain.
42. In Cloudflare, go to Workers & Pages → Create application → Workers → Start with Hello World!. Name it geo-sourcedcode-com and click Deploy.
43. Click Edit code, replace everything with Adobe’s Cloudflare worker, and click Deploy.
44. In Settings → Variables and Secrets, add two variables, then click Deploy:
- ORIGIN_HOSTNAME = main–geo-sourcedcode-com–sourcedcodecom.aem.live
- PUSH_INVALIDATION = enabled
45. Test the worker on its *.workers.dev URL, shown on the worker’s page.
Point the domain at the worker.
46. Go to sourcedcode.com → SSL/TLS and set Full (strict). Under Edge Certificates, turn on Always Use HTTPS.
47. In Caching → Configuration, set Caching Level to Standard and Browser Cache TTL to Respect Existing Headers.
48. In Caching → Cache Rules, create a rule: if the hostname equals geo.sourcedcode.com, it’s Eligible for cache, with Browser TTL set to Respect origin TTL.
49. In DNS, edit the geo record: a CNAME pointing to main–geo-sourcedcode-com–sourcedcodecom.aem.live, Proxied (orange cloud).
50. On the worker, go to Settings → Domains & Routes → Add → Route and add geo.sourcedcode.com/* on zone sourcedcode.com.
Purge the cache when you publish.
51. Create a Cloudflare API token at https://dash.cloudflare.com/profile/api-tokens with Zone → Cache Purge → Purge, scoped to sourcedcode.com. Copy your Zone ID from the zone’s Overview page.
52. Sign in at https://admin.hlx.page/login, then POST this to https://admin.hlx.page/config/sourcedcodecom/sites/geo-sourcedcode-com/cdn.json. Paste the token in yourself and never commit it:
|
1
|
{ “prod”: { “host”: “geo.sourcedcode.com”, “type”: “cloudflare”, “plan”: “free”, “zoneId”: “<zone id>”, “apiToken”: “<token>” } }
|
53. Check the credentials with the push invalidation tool.
Retire the old site from search.
54. geo-before.sourcedcode.com still allows indexing and still lists its own URLs in its sitemap, so search engines see the same content twice. In the Vercel project, add an X-Robots-Tag: noindex header, or a noindex robots meta tag, so only geo.sourcedcode.com gets indexed.
Part 13: Verify and Roll Back
55. Check the headers. You want 200 and server: cloudflare, with no x-vercel lines:
|
1
|
curl -sI https://geo.sourcedcode.com/geo-vs-aio | grep -iE “^(HTTP|server|x-vercel)”
|
56. Publish a small edit in da.live. It should appear on geo.sourcedcode.com within seconds.
57. In Google Search Console, resubmit https://geo.sourcedcode.com/sitemap.xml.
58. To roll back: delete the route and point DNS back at Vercel with a grey cloud.
Part 14: Record It
59. Paste this into Claude:
|
1
|
Create ai-docs/2026-11-08-eds-migration/ with prompts.md (every prompt I typed, verbatim), process.md, decisions.md and whats-left.md. Add ai-docs/ to .hlxignore. Tag v1.0.0 and push.
|
What Actually Happened on This First Migration
These numbers come from my Claude Code session log. Times are Pacific.
- Goal 1 (the pilot):
- About 1 hour, from 4:13pm to roughly 5:20pm on November 7.
- Goal 2 (the whole site):
- About 3 hours of actual work, spread over 2 days, because of the Pro usage limit.
- 5:26pm: started. The main session captured all 16 pages in about a minute, then began building the home page itself.
- 6:58pm: hit the 5-hour usage limit halfway through the home page’s gate.
- 8:12pm: resumed. The main session built the article and listing templates itself, one after another.
- 9:37pm: only now did it start 2 parallel agents, one for 4 landing pages and one for 8 articles. They finished in 17 and 25 minutes.
- 10:15pm: the main session ran the full verification sweep page by page for about an hour, and the conversation was compacted several times along the way.
- 10:18pm: I paused it.
- November 8, 10:31am: resumed, and it was done by 10:50am.
- Was it parallel? Only partly.
- 2 agents ran at once, for about 25 minutes.
- The 3 templates and the verification sweep ran on the main thread, one step at a time.
- Everything ran on Opus, including the agents.
- Measured in tokens read, the main session accounted for about 92% and the two agents about 8%. Every step re-read its large conversation, and that is what used up the Pro allowance.
What the Goal 2 in this post changes to avoid that: templates are built in parallel (Wave 1), pages are spread over 4 agents instead of 2 (Wave 2), verification is split across agents (Wave 3), agents run on Sonnet, the main thread only coordinates, and every config question is asked once, up front. I wrote it from that log and haven’t re-run the migration with it yet, so I can’t promise a time. It should spend far less of the Pro allowance per page, so it’s much more likely to finish in one or two usage windows.
Adding a page later: author it in da.live, then click Preview and Publish. It goes live on geo.sourcedcode.com with no code change. For a page that needs a new design, run /stardust:replica on its URL the same way as in Part 8.
Thinking About Moving Your Site to Edge Delivery Services?
If you have a Next.js, WordPress or other site you want to move to AEM Edge Delivery Services with da.live authoring, or you want to know how AI systems see your brand today, I’m happy to help. Get in touch.
See the migrated site

