Migrating a Corporate Marketing Site to AstroJS and Cloudflare Pages Using Claude Code

At Adalo we had for years used Webflow to manage the company’s marketing website. It’s a relatively large and complicated site, consisting of almost 1,000 blog posts, many Lottie file-based animations, and in some cases extraordinarily overengineered page layouts (such as https://www.adalo.com/pricing/). Webflow had served us well, however we’ve increasingly been using Claude Code for, well, pretty much everything and so figured it made sense to use it to manage the marketing site as well. Not to mention, Webflow was exceedingly expensive considering what it does, costing the company approximately $8,000 per year. Furthermore, Webflow restricted CMS access to a set number of seats (3 in our case), and we didn’t like that.

So we moved the site to AstroJS and hosted it on Cloudflare Pages. The total hosting cost is now $5/month, and everybody on our team can use their Claude Code subscription to modify and publish the site. Not only can they do it, but they are doing it! On some days, we see as many as a dozen new updates being deployed, and the best part is each team member can just run the AstroJS site locally on their laptop to see exactly what the site will look like once deployed to Cloudflare Pages via GitHub.

I’ve just finished the migration of another company marketing website, this time from the notoriously insecure WordPress hosted on the absurdly expensive WPEngine ($900/month). Because these migrations aren’t necessarily as straightforward as one might think, I’ve written up some notes here should you be interested in doing the same.

Step 1. Clone the Current Production Site

You’ll want to create a local clone of the current production marketing site in order to give Claude Code a reference. I don’t recommend asking Claude to do this for you because it will be slow and presumably burn through a ton of tokens. We have long maintained a simple Ruby script for this purpose, but there are plenty of OSS libraries available on GitHub which will get the job done.

After finding and installing an appropriate solution, open your terminal and create a directory, then run whatever command is required to download the site:

$ mkdir production-website
$ cd production-website
$ cloner https://www.example.com

Your chosen cloning library might be able to throttle requests; if so consider using it in order to reduce the possibility of web application firewalls being triggered due to your cloner looking like a bot. If your marketing site is sufficiently large then this cloning step could take as much as several hours to complete.

Step 2. Initialize Claude

Once the site has been cloned, open a terminal and navigate to the parent directory where the directory containing the clone resides. Open Claude Code, and once opened, run the /effort command, setting the effort level to xhigh. This will burn more tokens than you’re probably used to, but Claude will make much more reasonable decisions regarding the refactoring, ultimately saving you a lot of time. Then close Claude Code, because we need to make a configuration change that will be read the next time Claude starts.

Now open the newly created CLAUDE.md file and add a section titled Project Goal that looks somewhat like this:

## Project Goal

Convert a static WordPress snapshot of example.com into a clean, well-organized AstroJS site. The refactored site must be **pixel-perfect** against the example.com website, with strong SEO and consolidated assets.

**CRITICAL: Every single page must look identical to production example.com. Do not summarize, paraphrase, or approximate content. Extract the actual HTML from wp-snapshot/ and preserve it exactly — every image, every decoration, every SVG, every CSS class. If a hero has a photo with decorative arrows, coach headshots, and hand-drawn underlines, all of that must be in the Astro page. Compare against production visually before considering any page done.**

**NEVER claim a page matches production without first opening BOTH example.com AND localhost:4321 in Chrome (using the browser tools) and visually comparing them side by side. Take screenshots of both versions. If the browser tools are unavailable, explicitly tell the user you cannot visually verify rather than claiming a match. Making false claims about visual parity wastes enormous amounts of the user's time and erodes trust.**

Be sure to update the example.com placeholder with your current production website hostname.

Next, open the .claude/settings.local.json file and update the permissions JSON object to look like this:

{
  "permissions": {
    "allow": [
      "Bash(*)",
      "Read(*)",
      "Write(*)",
      "Edit(*)",
      "Glob(*)",
      "Grep(*)",
      "Agent(*)"
    ],
    "deny": [
      "Bash(rm -rf *)",
      "Bash(git push *)",
    ]
  }
}

Normally I would not recommend doing this because it is similar in risk to running Claude with the --dangerously-skip-permissions flag, however if you don’t give Claude more leeway to run a wide array of commands then you’re going to wind up spending the next several hours if not days constantly interactively granting Claude permission.

Next, return to or open Claude back up, but this time start it with the --chrome flag. Once open, run the /init command. Claude will respond by scanning the lone directory to learn more about the files and directories contained therein, and then will generate a CLAUDE.md file containing some information about what it found. After the CLAUDE.md file is written, you’ll tell Claude what you’d like to do:

We want to convert the WordPress site into an AstroJS site, along the way consolidatin redundant images, JavaScript, and CSS in order to ensure the site is very well organized. We also want to place great emphasis on SEO, and additionally ensure the refactored site looks *exactly* like the current production website, which you'll find at https://www.example.com. Update the @CLAUDE.md file to reflect this goal, and then begin the refactor.

The --chrome flag is required because you want Claude to open the browser to the production site you’re trying to clone alongside the locally running AstroJS site so it can easily compare the browser-based rendering of the two. This does not work by default; you need to configure it in conjunction with the Claude Chrome extension. Go to https://code.claude.com/docs/en/chrome for configuration instructions.

Running the AstroJS Site Locally

If you’re running Claude with the Chrome integration enabled, then Claude will probably start the local development server itself in order to view the site inside the browser. Even so, you should know how to start and stop the local server. To start it, run this command from inside the project directory:

$ npm run dev

Once started, you’ll be able to go to http://localhost:4321 in the browser to view the local site.

To stop the local server, return to the terminal and press Ctrl + c.

Ensuring the Site is Well Structured

AstroJS can organize a site in such a way that common layouts and components are managed in separate files and then inserted as needed. This is useful because if you want to change say the navigation bar or footer you only need to change a single file, and that change will automatically appear anywhere the respective component is referenced. Claude is pretty good at spotting opportunities for this type of consolidation, but even so make sure to provide it with some specific guidance here:

Audit the current site structure and reorganize it into a cleaner, maintainable Astro architecture using layouts, components, and shared content patterns.

Before making changes:
1. Inspect the repository structure.
2. Identify repeated markup, duplicated styling, inconsistent page structure, and opportunities for shared layouts/components.
3. Review existing Astro pages, components, CSS, config files, routes, content collections, and package scripts.
4. Do not change visual design, copy, routing, SEO metadata, or behavior unless needed to preserve consistency during the refactor.
5. Produce a short implementation plan before editing files.

Goals:
- Create or improve reusable Astro layouts in `src/layouts`.
- Extract repeated UI into focused components in `src/components`.
- Keep page files in `src/pages` thin and content-oriented.
- Preserve all existing routes and page URLs.
- Preserve existing styling unless there is obvious duplication that can be safely consolidated.
- Use Astro best practices: layouts for shared page shell/structure, components for reusable UI blocks, and slots/props where appropriate.
- Avoid over-engineering. Prefer simple `.astro` components unless client-side JavaScript is already required.
- Maintain accessibility semantics, heading order, alt text, labels, and keyboard behavior.
- Maintain existing SEO behavior including titles, descriptions, canonical links, Open Graph/Twitter metadata, sitemap behavior, and structured data if present.
- Keep imports clean and use project aliases only if already configured.

Suggested architecture:
- `src/layouts/BaseLayout.astro` for the global HTML shell, `<head>`, global metadata, header/footer wrapper, and `<slot />`.
- Additional layouts only if there are meaningfully different page types, for example:
  - `MarketingLayout.astro`
    - `BlogLayout.astro`
      - `DocsLayout.astro`
        - `LandingLayout.astro`
        - `src/components/common/` for small reusable primitives.
        - `src/components/sections/` for page sections reused across pages.
        - `src/components/navigation/` for header, footer, nav, breadcrumbs, etc.
        - `src/components/seo/` for SEO/head helpers if useful.
        - `src/styles/` for shared/global styles if the project already uses global CSS.

        Implementation requirements:
        1. Make the smallest safe refactor that materially improves maintainability.
        2. Extract components based on real duplication, not hypothetical future needs.
        3. Do not introduce a UI framework or styling system unless already present.
        4. Do not rename public routes or move files in a way that changes generated URLs.
        5. Do not delete content.
        6. Do not make broad formatting-only changes unrelated to the refactor.
        7. Update imports and references after moving/extracting code.
        8. Run the project’s validation commands after changes:
           - package manager install/check if needed
              - typecheck if available
                 - lint if available
                    - build
                    9. If a command fails, diagnose and fix it if the failure is related to your changes.
                    10. Leave a concise summary of what changed, why, and how to verify it.

                    Deliverables:
                    - A brief audit summary.
                    - A proposed component/layout map.
                    - The implemented refactor.
                    - A validation report showing commands run and results.
                    - A list of any follow-up improvements intentionally left out of scope.

                    Important:
                    - First, inspect and plan.
                    - Then implement.
                    - Then validate.
                    - Do not rewrite the site from scratch.
                    - Do not change the visual appearance unless unavoidable, and call out any unavoidable differences.

With this step complete it is time to start spot checking pages. It is the most tedious part of this project but at present an unavoidable reality.

Spot Checking and Submitting Fixes

I wish I could say Claude Code running in extra high effort mode was just going to do all of the hard work for you, but alas that’s not going to happen. You’re going to need to employ a combination of spot checks and prompts to bring this project across the finish line, and the challenge will be greater if your team has made the unfortunate decision of integrating a bunch of unnecessary JavaScript-driven animations and other effects into the current production site. I suggest starting with the AstroJS site’s home page, confirming the navigation bar layout matches production and (if they exist) dropdown menus are working. If they don’t match production, take screenshots of the local environment and production and paste them into Claude. You don’t need to describe the mismatch in detail, because the CLAUDE.md file already states your desire to create an exact match. Here is an example prompt I used to fix some issue in the navigation bar:

Production home page navigation bar: [Image #11] . Localhost: [Image #12] . 

BTW if you’ve never pasted screenshots into Claude Code, you can either drag them into the prompt window from your file explorer, or by pasting them in using Ctrl + v. yes, even on Macs you have to use Ctrl instead of Cmd. I have no idea why.

Continue working through the home page in this fashion, pasting in screenshots and adding supplementary commentary when required:

Production: [Image #18] Localhost: [Image #19] . Do not add the scroll-triggered entrance animation but at least make the sections look the same.

Once you’re happy with the home page, ask Claude to start working through the pages found in the header navigation bar and footer. While not required, starting at about this point in a refactoring project I like to ask Claude to start keeping notes about what it finds and how it resolved the issue:

Works great now, thank you. Now I would like you to go into each link found in the header navigation bar and the footer, and make sure the layout, css, and animations *exactl
y* match what is found on production. As you proceed, please update a file named @audit.md with your findings and what you did to fix the discrepancy.

Optimizing the Site

After the migration was complete I asked Claude Code to review the new codebase for further optimization opportunities:

Audit this codebase to see if anything can be optimized, paying special attention to image and CSS optimization. Organize your findings according to impact.

Here is an example of output Claude returned in response to this prompt:

✦ Claude Code

Here’s the optimization punch list, ordered by impact:

Critical

  • Image optimization — 125M of 154M build (81%) is unoptimized images in public/images/uploads/. Only 19% have WebP/AVIF variants. All <img> tags use raw src="/images/uploads/..." (e.g., src/pages/index.astro:69–95) instead of Astro’s <Image> component. Migrating to <Image> gets automatic WebP/AVIF, lazy-loading, and explicit width/height (fixes CLS too). Realistic savings: 20–40M.

High

  • Lottie outlierwavy-line.json is 7.9M (94% of lottie dir). If decorative, replace with CSS animation.
  • Inline style="font-weight: 400" attributes — 751 instances scattered across page content (WordPress export leftovers). Move to CSS classes; cleaner DOM and smaller HTML.
  • Dead CSS — ShantellSans @font-face at src/assets/css/global.css:58,62 and --tr-font-accent (line 85) are declared but never referenced by any selector. Drop the font (428K) and the variable.

Medium

  • Font duplicationsrc/assets/fonts/ and public/fonts/ hold identical copies (~780K duplicated). Consolidate to public/fonts/ only.
  • !important overuse — 9 occurrences in global.css (lines 235–238, 311, 362, 419, 425, 431) from WP-legacy resets. Refactor with selector specificity.
  • Large CSS filescomponents/navigation.css (1,505 lines) and components/sections.css (1,108 lines) are big enough to warrant a scan for unused/duplicate selectors (PurgeCSS or manual).

Low / cleanup

  • Redundant redirect files — 292 public/index.html?p=*.html meta-refresh files duplicate what public/_redirects already does (and _redirects issues proper 301s). Delete once you’ve confirmed Netlify/Cloudflare is canonical.
  • Tracking docsaudit.md (55 lines) and progress.md (277 lines) at repo root are migration logs. Archive or delete.
  • Migration scriptsscripts/convert-*.mjs, consolidate-images.mjs, generate-redirects.mjs were one-shot tools. Confirm they’re not in any build pipeline, then either delete or move to an archive/ folder.

Clean (no action)

  • JavaScript — main.js is vanilla; jQuery only appears in a comment.
  • Astro imports — no unused imports detected in sampled pages.

Want me to start working through any of these? Image optimization is the highest-leverage but biggest job; the font/Lottie/redirect-file cleanups are quick wins.

You can also ask Claude to annotate a log file, explaining the nature of each optimization problem and how it was fixed. Here is an example of this approach from my recent migration project:

### Image optimization — DONE (2026-05-15)
- **Original problem:** 125M of 154M build (81%) was unoptimized raster images in `public/images/uploads/`; only 19% had WebP/AVIF variants.
- **Approach taken:** Rather than migrate 2,201 `<img>` tags across 156 files to Astro's `<Image>` component (which would have broken stable WordPress URLs), generated AVIF + WebP siblings next to each raster file and rewrote `<img>` tags into `<picture>` elements with AVIF / WebP `<source>` tags + the original as fallback.
- **Scripts:**
  - `scripts/generate-image-variants.mjs` — sharp-based, idempotent (skips up-to-date variants), 8-way concurrent.
    - `scripts/img-to-picture.mjs` — regex codemod over `.astro` files in `src/pages|components|layouts`. Adds `decoding="async"` if missing. Preserves existing `loading` attributes (no auto-lazy, to protect LCP).
    - **Results:**
      - 500 raster sources (89.5 MB) → 500 WebP (22.8 MB) + 500 AVIF (14.2 MB).
        - 1,321 `<img>` tags rewritten across 136 files.
          - Build clean (412 pages in 6.11s). Browser serves AVIF on `/` and `/coaching-services/`; 0 broken images.
          - **User-facing payload reduction:** ~84% per image (AVIF vs raster). Originals remain on disk as fallback for legacy browsers.
          - **Re-running:** Both scripts are idempotent. Re-run after adding new images / new pages.

Deploying to CloudFlare Pages

Whenever you’re feeling reasonably comfortable about the AstroJS clone matching the current production marketing site, commit your changes and push to GitHub. Then create a CloudFlare account if your company doesn’t already have one.

TODO