Developer hiring reference

WordPress to Astro Migration Brief

The job is to move mobilebillboardsf.com off WordPress and onto a static Astro site hosted on Cloudflare Pages, deploying from GitHub. This is the exact process we used for mobilefoodexperts.com, and every other Boki business site runs the same stack.

Use it two ways: as the standard a candidate should be able to describe and reproduce, and as the source for the interview questions and the practical test in sections 6 and 7.

Site to migrate
mobilebillboardsf.com
WordPress + Elementor. 13 pages, 4 blog posts, 2 GoHighLevel embeds.
Reference build
mobilefoodexperts.com
WordPress to Astro, March 2026. 194 commits since.
Second site on same stack
fallsfireprotection.com
Fresh Astro build, live April 2026.

1. The site being moved

What a crawl of mobilebillboardsf.com shows today. Hand this inventory to the candidate as part of the practical test, then check whether they find anything it misses.

ItemCurrent state
PlatformWordPress with Elementor 3.34, Hello Elementor child theme, All in One SEO 4.9, LiteSpeed cache. Already fronted by Cloudflare.
BusinessLED truck advertising in Sioux Falls, SD. Co-owned with Mirko Spiric. Starter package $995. Premium positioning against Lamar and BookYourBillboard.
Lead captureGoHighLevel. Booking widget on /calendar/ and /download-pricing/, survey widget on /schedule/, both loaded via link.msgsndr.com/js/form_embed.js.
Subdomain to preserveaccount.mobilebillboardsf.com resolves and is linked from the homepage. Its DNS record must survive the cutover.
TrackingNo GTM, GA4 or Meta Pixel tags detected on the homepage. All of it gets installed fresh.
MediaRoughly 15 images on the homepage, favicon uploaded November 2025. Gallery page has no third-party embeds.
RobotsBlocks /wp-json/, feeds, tags, categories and search. The sitemap index is referenced but returns empty to non-browser requests.

Every URL that has to map somewhere

Thirteen pages and four posts. All of these need either a rebuilt page or a 301 in the redirects file. Posts sit at the root with no /blog/ prefix, which is worth keeping so no post URL changes.

TypePathNotes
Page/H1 "Mobile Billboard SF in Sioux Falls". Sections: truck intro, service packages, FAQ, truck gallery.
Page/about-us/
Page/our-team/
Page/mobile-billboard-truck-route/Route information. Likely a map.
Page/led-screen-trucks-faqs/Candidate for FAQPage schema.
Page/gallery/Image heavy. WebP conversion matters most here.
Page/vip-perks/
Page/mobile-billboard-ads-blog/Post listing. Becomes the resources index with CollectionPage schema.
Page/calendar/GHL booking widget r6UtycCT0G1VJdgsJfXW.
Page/download-pricing/Same GHL booking widget.
Page/schedule/GHL survey widget 1vIzUIMaedPsriZrJiPM.
Page/privacy-policy/
Page/terms-and-conditions/
Post/mobile-billboards-how-they-work-cost-and-effectiveness/
Post/mobile-billboards-vs-traditional-billboards/
Post/the-local-guide-to-effective-mobile-billboard-advertising-in-sioux-falls/
Post/the-top-5-benefits-of-mobile-billboard-advertising-for-local-businesses-in-sioux-falls/

The homepage links to /calendar, /schedule and /download-pricing without trailing slashes, and WordPress 301s them. On the new site those links get the slash directly so no redirect is needed.

2. The target stack

A candidate should recognise every row here and be able to explain why each piece is there. This is what "done" looks like for us.

LayerWhat we useWhy it matters
FrameworkAstro 5, output: 'static'Pure static HTML. No server, no database, no plugin updates.
Source controlGitHub, private repo under the mobilefoodexperts orgThe repo is the single source of truth. The main branch is production.
HostingCloudflare Pages, connected to the GitHub repoEvery push to main builds and deploys in about 60 seconds. Free tier.
Build settingsPreset Astro, command npm run build, output distPackage.json must sit at the repo root, or the root directory has to be set in Cloudflare.
DNS and SSLCloudflare DNS, certificate issued automaticallyDomains are registered at Porkbun and pointed at Cloudflare nameservers. Falls Fire Protection is the exception, bought at Cloudflare directly.
URLstrailingSlash: 'always', sitemap from @astrojs/sitemapPrevents 308 redirect chains. Every internal link ends in a slash.
Redirectspublic/_redirectsOld WordPress slugs 301 to the new pages. Cloudflare Pages reads this file natively.
Security headerspublic/_headersContent Security Policy, nosniff, frame options. Never set in astro.config.
AnalyticsGoogle Tag Manager, GA4 configured inside GTMGTM snippet in the head, noscript after the body opens.
PixelsMeta Pixel and Microsoft Clarity hardcoded in BaseLayoutMeta Pixel through GTM fired unreliably for us, so it goes direct.
Lead captureGoHighLevel forms, booking widget, chat widgetEmbedded as iframes or scripts, so CSP has to allow the GHL domains.
SEO filesrobots.txt, sitemap-index.xml, llms.txt, JSON-LD schemaOld /wp-* paths stay blocked in robots. LLM crawlers explicitly allowed.
AuditingSearch Atlas crawl, plus Node scripts hitting PageSpeed, Search Console and GA4 APIs via a Google service accountBefore and after numbers for every SEO or performance change.
Editing workflowClaude Code in the project folder, a CLAUDE.md context file in the repo rootNot required for the developer, but they should be comfortable with the repo being edited this way.

3. The migration process

These are the phases in the order we ran them. The order matters: SEO foundations and tracking go in before launch, and the redirects go live the same minute the DNS switches.

  1. Export and inventory the WordPress site

    • Tools → Export → All content. This produces one WXR XML file. Ours was 1 MB, dated the day before the first Astro commit.
    • Download the media library, or at minimum every image actually used on a page.
    • Pull the full URL list from two places: the WordPress sitemap and Google Search Console's pages report. GSC catches junk URLs that still get impressions, such as /home-7891-6896 and /book-an-appoinment-7238, which a sitemap will not show.
    • Note every form, booking widget, tracking script and third-party embed on the old site. These are the things that silently break in a migration.
  2. Plan the new site before writing code

    • Brand foundation: colours, heading and body fonts, a transparent-background logo at 600px or wider.
    • Page map with a meta title under 60 characters and an H1 for every page.
    • CTA hierarchy: the one action a visitor should take, then the second.
    • A URL mapping table, old slug to new slug. This becomes the redirects file later.
  3. Set up the environment and the repo

    • Node LTS, Git, a GitHub account with access to the org.
    • npm create astro@latest, then add @astrojs/sitemap and sharp for image processing.
    • Commit early. Our first commit was the scaffold, the second was the gitignore. Never commit node_modules or dist. The MFE repo had to fix that on day three.
    // astro.config.mjs — the three settings that are not optional
    export default defineConfig({
      output: 'static',
      site: 'https://yourdomain.com',
      trailingSlash: 'always',
      integrations: [sitemap()],
    });
  4. Build the layout and rebuild the pages

    • One BaseLayout.astro owns the head: charset, viewport, description, dynamic canonical from Astro.url.pathname, Open Graph, Twitter card, favicons, webmanifest, preconnects, fonts loaded with link tags and an async onload pattern, never @import.
    • Header and Footer as components. Footer carries the address as a Google Maps link, phone, email and social icons.
    • Pages rebuilt one at a time from the WordPress content. Copy is cleaned, not pasted with WordPress markup and inline styles.
    • Every image converted to WebP at 80% quality, with explicit width and height. Hero and logo load eagerly with fetchpriority="high", everything else lazy.
    • A branded 404 page. Skip-to-content link and ARIA on the mobile menu toggle.
  5. Lay the SEO foundation

    • robots.txt allows everything, blocks the old /wp-content/, /wp-admin/, /wp-includes/, /wp-login.php and /xmlrpc.php paths, explicitly allows GPTBot, ClaudeBot, Google-Extended, CCBot and anthropic-ai, and points at sitemap-index.xml.
    • JSON-LD on every page: LocalBusiness or ProfessionalService, Organization with all social profiles in sameAs, BreadcrumbList. Page-specific: FAQPage, Article, CollectionPage, ContactPage, Person, WebApplication for calculators.
    • public/llms.txt listing every page and article URL.
    • Hard limits: meta title 60, OG title 60, Twitter title 55, Twitter description 125, meta description 160. No "Brand | Brand" duplicate in titles.
  6. Install tracking

    • GTM container snippet in the head, noscript iframe right after the body opens.
    • GA4 configured as a Google Tag inside GTM with an all-pages trigger, then published.
    • Meta Pixel and Microsoft Clarity pasted directly into BaseLayout, not routed through GTM.
    • Verify in GA4 realtime and Meta Events Manager before launch, not after.
  7. Write the security headers

    A single public/_headers file. The CSP has to allow every third party the site actually loads: Google Tag Manager, Google Analytics, Facebook, GoHighLevel's api, widgets and services domains, the FastPayDirect payment host, YouTube frames and Google Fonts. Add nosniff, SAMEORIGIN, a strict referrer policy and a permissions policy that turns off camera, microphone and geolocation.

    A developer who ships a CSP without testing the GHL booking widget will break lead capture on launch day.

  8. Deploy to Cloudflare Pages

    • Push the repo to GitHub. In Cloudflare: Workers & Pages → Create → Pages → Connect to Git → pick the repo.
    • Framework preset Astro, build command npm run build, output directory dist. Save and deploy. Cloudflare gives a *.pages.dev preview URL immediately.
    • Review the site on the preview URL. Test every form, the booking widget, the chat widget and the tracking on the preview before touching DNS.
  9. Cut over the domain

    • Move the domain's nameservers to Cloudflare if they are not already there. Wait for the zone to activate. mobilebillboardsf.com already sits behind Cloudflare, so this step is only a check that the account subdomain record and the mail records stay intact.
    • Pages project → Custom domains → add the apex domain and www. Cloudflare writes the DNS records and issues the certificate automatically.
    • The _redirects file must already be in the repo at this point. Old slugs 301 to new pages, old /blog/ to /resources/, old /about/ to /about-us/, and any slash-less versions of new URLs to their slashed form.
    • Google Search Console: verify the domain property, submit sitemap-index.xml. Update the Google Business Profile website link.
    • Keep the WordPress host alive for a couple of weeks in case something was missed, then cancel it.
  10. Audit, fix, and set the ongoing workflow

    • Run a Search Atlas crawl, export the issues list, fix everything at severity 8 and above, re-crawl. Target 950+ per page.
    • Run PageSpeed on every page in the sitemap, mobile and desktop. Fix layout shift first. Our biggest win was a sitewide CLS of 0.275 caused by a translateY fade-in animation. Removing it took CLS to zero.
    • Set up a Google Cloud service account with PageSpeed, Search Console and Analytics Data APIs, grant it viewer access in GSC and GA4, and keep the JSON key outside the repo. Audit scripts save before and after JSON so every change is measurable.
    • Ongoing change workflow: edit, npm run build locally, review titles and descriptions, commit, push. Cloudflare deploys in about a minute. Re-crawl if SEO changed.

4. How it went for MFE

The commit history of the MFE repo shows the real pace. A candidate who has done this before will recognise the shape: a burst of scaffolding, then a dense SEO and tracking day, then a week of audit-driven fixes.

DateWhat landed
Mar 3, 2026WordPress export taken. Astro scaffold committed.
Mar 4Repo on GitHub, gitignore, CLAUDE.md context file, first Cloudflare deploys. Team page, coach photos, signup URLs.
Mar 6GTM installed. Meta titles, descriptions, canonicals, OG and Twitter tags. Sitemap, robots.txt, structured data. Favicons, 404, FAQ schema, webmanifest. Images to WebP. Accessibility and CLS prevention. Node_modules and dist removed from tracking. Resources section and first three articles.
Mar 10 to 11Audit-driven fixes: title length, description trims, llms.txt, inline styles moved to classes, Meta Pixel added direct, orphan pages linked, trailing slashes fixed sitewide, breadcrumb and ContactPage schema, Google Maps links, one build error fixed by moving schema computation into frontmatter.
Mar 22Performance pass: CLS 0.275 to 0, fetchpriority on hero images, async font loading, unused font weights trimmed, calculator accessibility 84 to 93. Audit scripts for PageSpeed, GSC and GA4 committed.
SinceRedirects for old WordPress URLs found in GSC. Articles, landing pages and calculators added weekly. 194 commits total.

Realistic effort: the core MFE migration was three working days for a site of similar size. Falls Fire Protection, a new build on the same stack, took 23 commits. Mobile Billboard SF at 17 URLs with two GHL embeds and a gallery should land in two to three days for someone who has done it before.

5. Details that prove experience

Anyone can follow an Astro tutorial. These are the things we learned in production. A strong candidate will volunteer several of them unprompted.

6. Interview questions

Ask these in order. Each one has a specific answer we already know from doing the work, so a vague reply is easy to spot. Do not lead with the answer.

WordPress side

You have admin access to a WordPress site that needs to become a static Astro site. What do you take from it, and how?

Strong

WXR XML export from Tools, the media library, the full URL list from the sitemap and from Search Console, and an inventory of forms, embeds and tracking scripts. Mentions checking for plugin-generated pages and junk slugs.

Weak

"Copy the text from each page." No mention of URLs, media or third-party scripts.

After launch, Search Console shows impressions for URLs that do not exist on the new site. What happened and what do you do?

Strong

Those are old WordPress URLs still in Google's index. Add 301s in the redirects file to the closest new page. Keep /wp-* blocked in robots. Expect it to take weeks to clear.

Weak

"Ask Google to remove them" or "they will go away on their own."

Astro

What goes in astro.config.mjs for a static marketing site on Cloudflare Pages, and why?

Strong

Static output, the site URL so the sitemap and canonicals resolve, trailingSlash: 'always' to avoid redirect chains, the sitemap integration. Should be able to say what happens to internal links if the trailing slash setting is inconsistent.

Weak

Cannot name the trailing slash setting or does not know why it matters.

Where do the canonical URL and og:url come from on each page?

Strong

Built in the base layout from Astro.url.pathname joined to the site origin. Never hardcoded.

Weak

Hardcodes the homepage, or does not know the difference between canonical and og:url.

PageSpeed reports a cumulative layout shift of 0.27 on every page. Where do you look first?

Strong

Images without width and height, web fonts swapping in, and scroll or load animations that move elements with transform. Knows opacity-only animation is the fix for the last one.

Weak

"Add lazy loading" as the only answer, or does not know what CLS measures.

GitHub and Cloudflare

Walk me through connecting a GitHub repo to Cloudflare Pages and getting a custom domain live.

Strong

Connect to Git, choose repo, Astro preset, npm run build, dist, root directory if package.json is nested. Preview on pages.dev, then Custom domains adds DNS and SSL automatically when the zone is on Cloudflare. Knows nameservers may need to move first.

Weak

Describes uploading a zip, or cannot say what the build output directory is.

Where do 301 redirects and security headers live on a Cloudflare Pages static site?

Strong

_redirects and _headers in public/, copied into the build output. Knows a CSP has to list every third-party script and frame host the page uses.

Weak

Wants to put them in astro.config, or suggests a WordPress-style plugin.

A push to main fails the Cloudflare build. What is live right now and what should have happened before the push?

Strong

The previous deploy stays live. The developer should have run the build locally first. Reads the Cloudflare build log to find the error.

Weak

Thinks the site goes down, or has no local build habit.

Tracking and integrations

How do you install GTM, GA4 and the Meta Pixel on an Astro site, and in what order?

Strong

GTM snippet in the head and noscript after body in the base layout. GA4 as a Google Tag inside GTM, published. Pixel hardcoded in the layout. Verifies with GA4 realtime and Meta Events Manager before launch.

Weak

Reaches for a WordPress plugin, or does not know GTM needs publishing.

The site embeds a GoHighLevel booking calendar on two pages and a survey on a third. What can break those in a migration?

Strong

CSP blocking the GHL script and frame domains. Form redirects or thank-you pages that changed URL. Unpublished GHL workflows. Tests each on the preview URL.

Weak

"Paste the embed code" with no awareness of headers or testing.

Working style

We edit these repos with Claude Code, and there is a CLAUDE.md context file at the root. How would you work alongside that?

Strong

Keeps the context file current when conventions change, follows the conventions in it, keeps commits small and descriptive so the history stays readable.

Weak

Dismissive, or wants to restructure the project before understanding why it is shaped this way.

7. Practical test

A paid trial on the real job beats any interview. Give the candidate the WordPress export of mobilebillboardsf.com, the logo and truck photos, the inventory in section 1, and this brief. Time box: three working days. Stage one is the first day and is enough to decide whether to continue.

Stage one, day one: foundation

Stage two, days two and three: the rest

How to grade it

Do not hand over Cloudflare, GitHub org, GHL or Google credentials for the trial. They deploy to their own Cloudflare account and their own GitHub, and the live site stays untouched. If hired, the repo transfers into the mobilefoodexperts org, the Pages project is recreated under Boki's account, and the DNS cutover happens with Boki present.

8. Scoring sheet

Score each area 0 to 3. Under 14 total is a no. Weight the practical test heaviest.

Area03
WordPress export and URL inventoryCopies page text by handWXR export, media, sitemap plus GSC URL list, embeds inventoried
Astro fundamentalsCannot explain the configStatic output, trailing slashes, dynamic canonicals, layouts and components
GitHub and Cloudflare PagesManual uploadsGit-connected auto deploy, custom domain, preview URLs, clean history
SEO migration hygieneNo redirects, default robots301 map from GSC, WP paths blocked, schema, sitemap submitted
Headers and CSPUnawareWritten in _headers, tested against every embed
TrackingPlugin mindsetGTM plus GA4 inside it, pixel direct, verified before launch
PerformanceNo measurementWebP, dimensions, font loading, CLS fixed, before and after numbers
Practical testNot delivered in the time boxAll deliverables, PageSpeed bar met, readable README
Sources: the MFE Astro repo and its CLAUDE.md, the astro-website-builder playbook, the Falls Fire Protection technical build spec, the Mobile Billboard SF wiki page, and a live crawl of mobilebillboardsf.com on September 2, 2026. Passwords and API keys are intentionally left out of this document.