I've just moved amandalucas.eu off WordPress and onto Etch Studio. The site was first rebuilt in Etch on WordPress on my dev site, dev.itchyfingersdesign.com, and then I brought it into a Studio project and deployed it to Cloudflare.
The whole thing went live in a day. Some of it was smooth and some of it wasn't, so here's the workflow I used and the lessons I'd pass on to anyone else doing the same.
What Etch Studio gives you
Etch Studio builds a static site. There's no WordPress behind it, no database and no PHP. That's the big win for speed and maintenance, but it also means a few things work differently:
- There's no import from an Etch WordPress site into Studio. You rebuild. I rebuilt my templates and pulled the content across, but it's still a rebuild.
- Blog posts live in a content type. I made an "Article" content type with one record per post.
- Cloudflare Workers have a 25 MB limit for the whole site, so images matter.
- Anything that needs a server (forms, redirects based on logic) needs a different approach.
Step 1: Set up the project folder and the Etch connector
I keep my Studio work in an Etch Sites/projects folder on my Mac, so I made an amandalucas folder there from my project template.
I used Claude to do a lot of the heavy lifting on the build, through the Etch connector. If you want to do the same:
- Turn on the AI Connector in your Studio project settings.
- Run
npx @digital-gravy/etch-connector servefrom your project folder and leave it running. - Open the project in Studio and click the AI button in the left sidebar to connect.
Two things caught me out here. The connector stops when your Mac sleeps or the session ends, so you'll be restarting it and clicking the AI button again more than once. And after I imported my ACSS settings, Studio refused the connection with "Clients cannot use the AI Connector" until I reloaded the tab and reconnected as the project owner.
Step 2: Bring the content and design across
I pulled all the content from the dev site through the WordPress REST API: 24 posts, the pages and the categories. Then the build went in this order:
- Global styles: colours, spacing, radius and fonts.
- Components: header, footer and a post card.
- Templates: the index template (site-wide head, header and footer), an article template and a topic template.
- Pages: Home, Blog, About, Contact and the legal pages.
- Article records: one per post, each at the same root-level slug as the old WordPress post, for example
/css-page-load-transitions/. Keep your URLs identical and you don't need a pile of redirects at launch. - Topic pages at
/category/<slug>/to match the old category archives. - SEO titles and descriptions on every page, taken from a sitemap export I'd made of the live site.
A few lessons from this part:
- Rebuild article bodies as native Etch elements. The first pass dropped the WordPress HTML into raw HTML blocks. It works, but you lose control of the styling. Native paragraphs, headings, lists, code blocks, tables and images are worth the extra effort.
- Curly braces in code blocks. Etch reads
{ }as dynamic data, so code snippets need the braces escaped or they disappear. - Scripts in raw HTML blocks turn into text. Load scripts through the template head instead.
- Convert images to WebP. Mine went from 5.8 MB down to about 1.2 MB once I swapped two big GIFs for static images.
- Click Save in the builder so the styles compile before you check anything.
ACSS in Studio
I build with Automatic.css, and the first version of the Studio site had my colours and spacing recreated by hand, not running ACSS at all. Studio has an import for ACSS settings, so I exported acss-export.json from the ACSS dashboard on my dev site and imported it in Studio.
That's when the colours went wrong. In my ACSS, primary is yellow and secondary is near-black, but the hand-made styles used "primary" for the near-black. The two fought each other. Once every style pointed at the ACSS names, the ACSS settings drove all the colours properly. If you're going to use ACSS, import it before you build any styles and save yourself the clean-up.
Step 3: Deploy to a Cloudflare Worker first
Before touching the domain, deploy to a test address:
- In Studio, go to Settings > Deployment > Connect Cloudflare.
- Approve it in the Cloudflare window and give the worker a name. Mine is
amanda-lucas. - Deploy. You get a workers.dev address to check the site on.
Check everything on that test link: every article URL, the sitemap, the contact form, code blocks and the mobile menu. This costs nothing and doesn't touch your live site.
Step 4: Point the domain at the Worker
My DNS was already on Cloudflare, which made this quick. Setting the domain in Studio's settings tells the build its own address, but it doesn't move the domain on Cloudflare. That part happens on the Worker.
- In Cloudflare, open your domain, then DNS > Records.
- Find the old A record for your root domain. Write down its IP address. That's your way back to the old site if anything goes wrong. Then delete it, along with any AAAA record for the same name.
- Leave MX and TXT records (SPF, DKIM, DMARC) alone. Your email depends on them.
- Go to Workers & Pages > your worker > Settings > Domains & Routes, click Add > Custom Domain and enter your domain.
If you try step 4 first, Cloudflare tells you the hostname "already has externally managed DNS records". That's the old record in the way. The site is offline for a minute or two between the delete and the add, so do them back to back.
Because my domain was set in Studio's settings, the next deploy from Studio attached the custom domain to the worker for me.
The www version
Adding www as a second Custom Domain on the worker didn't work for me. What did work:
- Add a DNS record: type CNAME, name www, target your root domain, orange cloud on.
- Add a Redirect Rule under Rules > Redirect Rules: if Hostname equals
www.yourdomain.com, redirect with a 301 toconcat("https://yourdomain.com", http.request.uri.path)and tick Preserve query string.
Check the filter carefully. My first version caught the root domain as well as www, and every page redirected to itself. The site was down with "too many redirects" until I switched the rule off and fixed the expression to (http.host eq "www.yourdomain.com").
Step 5: Contact form
A static site can't process a form on its own, so you need a form service. I use OnePage CRM, and its Web Forms feature gives you an embed code. Each enquiry lands in the CRM as a contact. Web Forms need the Professional plan or higher. If you don't use OnePage, Web3Forms has a free plan that does the job.
I put the embed in an element on the Contact page. It only loads once the site is deployed, so don't panic when it doesn't show in the builder.
Step 6: Analytics and cookies
I'm in Ireland, so GA4 can't load until a visitor says yes. My setup:
- Google Consent Mode v2 with everything set to denied by default, in the head of the index template.
- CookieConsent v3, a free open-source banner, with Accept and Reject buttons given equal weight, and a "Cookie settings" link in the footer.
- The GA4 tag after that, so nothing reaches Google until someone accepts.
- Cloudflare Web Analytics as well. It sets no cookies, so it counts the people who click Reject.
Expect lower numbers in GA than you had before, since it only counts people who accept.
I also took the chance to cut my policies down. I now have a Privacy & Cookies page and a short Disclaimer. The old cookie policy URL redirects to the cookies section of the privacy page. I also self-host my fonts, so no visitor IP addresses go to Google Fonts, and the privacy page says so.
Step 7: Unpublishing out-of-date posts
Some of my older articles are out of date and I don't want them live until I've updated them. Studio has no "draft" status for content records out of the box, so I added one.
Add the custom field
- Go to Content, open your Article content type and add a new field.
- I first tried a Checkbox field, but Studio wouldn't accept it. A Yes/No field works fine. Mine is called Status: Draft and it's off by default on every article.
- To unpublish a post, open the article record, switch on Draft, save, then deploy.
Filter it out of the grids
Every loop that lists articles needs a condition that skips anything with Draft switched on. On my site that's the home page grid, /blog/, the topic pages, the topic post counts and the Previous/Next links on each article. Miss one and the draft still shows up somewhere.
The route and the draft's own URL
This was the part I expected to be simple. Studio's dynamic routes can't skip a record based on a field value, so the draft article's page still gets built at its URL. To deal with that:
- The article template checks the Draft field. If it's on, the page shows a short "This article is being updated" message, is set to noindex, and sends visitors on to /blog/.
- On top of that, each draft gets a proper 302 redirect to /blog/. Use 302 (temporary), not 301. A 301 tells Google the page has gone for good, and I want these URLs back at the same address once they're updated.
- When I switch Draft off on an article, its redirect has to come off before the deploy, or the republished post just bounces to the blog page.
One thing I haven't solved yet: the sitemap still lists the draft URLs. That's a Studio limit for now.
Step 8: Small things worth making dynamic
My About section said "70+ articles" and "12 years". Both go stale, so I made them dynamic:
- Article count comes from a count of published articles, so it drops when I switch a post to draft.
- Years in business is worked out from 2014, when I started. Because the site is static, a build-time number only changes when you deploy, so a tiny script on that element works it out in the browser. It ticks over to 13 in January without a redeploy.
Things that broke after launch (so you can check for them)
- Card styles came unlinked on a deploy. One deploy left my post cards as plain links and images with no card layout, and every card showed two titles because the hover overlay title was no longer hidden. If your cards suddenly look odd after a deploy, check the component's styles are still linked to it.
- My direction-aware hover disappeared with the same deploy and had to be put back.
- Masonry columns put my posts in the wrong order. The articles were sorted newest first, but the CSS columns layout fills each column top to bottom. So the top row showed the 1st, 9th and 17th newest posts. I switched to a grid that fills row by row (3 across on desktop, 2 on tablet), and now the newest post sits top left.
- Sort by the original publish date. Give each Article record the original WordPress publish date in a date field and sort every loop by it, otherwise you get the order the records were created in.
- Fake bold headings. Questrial only comes in one weight, so a heavier heading weight made the browser fake it. Set headings to 400.
My launch checklist
- Every old URL returns a 200 on the workers.dev test link
- Old A record IP written down before you delete it
- MX and email TXT records left alone
- Custom Domain on the worker shows as Active
- www CNAME plus a redirect rule that only matches www
- robots.txt allows crawling and points to the sitemap, and no page has a stray noindex
- Contact form loads and submits on the live site
- Cookie banner: nothing sent to Google before a choice or after Reject
- Cloudflare Web Analytics switched on
- Mobile menu and logo link checked on a real phone
- Card styles still linked after each deploy
Would I do it again?
Yes. The site is faster, and there's no WordPress to update. If I were starting over I'd import ACSS first, set up the draft field before adding any content, and build article bodies as native Etch elements from day one.
If you're moving a site into Etch Studio and get stuck on any of these steps, get in touch and I'll try to help.