Ten months of migrating WordPress to Astro on Cloudflare Pages

Ten months of migrating WordPress to Astro on Cloudflare Pages

Updated: October 24, 2026
18 min read
Case study
500+ WP projects

The replatform from WordPress to Astro was supposed to be the project. It turned out to be the prologue. Exporting the content, rebuilding the templates, getting a static site to compile and deploy to Cloudflare Pages took weeks. Then the real months began: redirects, hreflang, six-locale parity, and a build that outgrew the platform it was deployed to. This is a report on where the time went, because the time did not go where the planning said it would.

The polemic, if there is one, is with the framing of replatforming as a port. “Move off WordPress to a static site” sounds like a one-time migration. For a multilingual content site it is closer to taking ownership of three systems WordPress used to hide: the routing layer, the build, and the cross-locale structure. None of them are hard. All of them are continuous.

[!NOTE] Case at a glance

  • Project: wppoland.com replatformed from WordPress to Astro on Cloudflare Pages, a first-party rebuild of my own site
  • Scope: six locales, 15,718 prerendered pages and 8180 URLs in the sitemap (build of 20 October 2026)
  • Timeline: started in December 2025 (on 2 December the site still ran WordPress with WPML and WP Rocket, per the Wayback Machine; the Astro repository was created on 21 December), weeks to a working build, first version of this report after five months, this update after ten
  • Build: exceeded Cloudflare Pages’ 8GB runner ceiling, resolved by building locally with a 16GB heap and deploying the artifact with Wrangler
  • Redirects: 301 rules that hit Cloudflare’s 100KB _redirects cap; today 3619 of them live in a map in Cloudflare Functions and 249 in _redirects
  • Stack: Astro with Tailwind CSS, AVIF image pipeline, static HTML served from the edge
  • Outcome: PageSpeed (lab, mobile, 20 October 2026) 94 for the Polish homepage and 98 for the Astro service page, no PHP or database in production, six-locale parity held

#How much does a WordPress to Astro migration cost

  1. The port is the cheap part. Templates and content export took weeks; ten months in, the long tail of the migration still needs fixes.
  2. The redirect layer is the first surprise. Thousands of previously indexed URLs each need a 301, and the volume collided with a Cloudflare Pages file-size cap that silently dropped rules.
  3. Six-locale parity is continuous work, not a task. Hreflang, canonical URLs, and section structure have to stay aligned across every language version, forever.
  4. The build outgrew Cloudflare’s own runner. An 8GB build ceiling is not enough for more than 15,000 prerendered pages; the answer was to build locally with a 16GB heap and deploy the artifact via CLI.

#Glossary: static build, prerender, hreflang, edge

The report rests on a few platform terms:

  • Static build - the whole site is rendered to plain HTML files ahead of time, during a build step, rather than per request.
  • Prerender - generating each page’s complete DOM tree into physical HTML files. A six-locale site multiplies page count by the number of locales, so the build scales with content times languages.
  • Cloudflare Pages - the hosting platform that serves prebuilt files from the global edge network and runs serverless logic via Pages Functions.
  • Wrangler - Cloudflare’s command-line tool, used here to deploy a locally built dist/ directory directly, bypassing the platform’s build step.
  • Hreflang - HTML header attributes that tell search engines which URL is the local equivalent in another language.
  • 301 redirect - a permanent HTTP redirect that carries a moved URL’s ranking signal and index history to its new address.

#How long it takes to port WordPress to Astro

The visible migration is the part that gets estimated, and the estimate is roughly right. Content comes out of WordPress MySQL tables into Markdown files with YAML frontmatter. PHP templates and child themes are rewritten into Astro components styled with Tailwind CSS. The build compiles, and the static files deploy to Cloudflare Pages. A content site of moderate size reaches a working static build in weeks. This is the phase that demos well and convinces leadership the project is nearly finished.

In reality, the project is only at the beginning of its true engineering phase. A working build proves that Astro components can assemble HTML without syntax errors. It proves nothing about whether thousands of historical URLs still resolve properly, whether hreflang relationships remain intact across international Google indexes, or whether the Node.js build process can handle further corpus growth.

#Redirecting old WordPress URLs after moving to Astro

After the build ran, redirects came next. Every URL WordPress had ever exposed since 2006 (including date archives, categories, author tags, plugin taxonomies, and legacy slugs) needed a precise 301 redirect to its new Astro address. Without an exhaustive mapping layer, 404 errors surged in Google Search Console and historical search authority dissolved.

On a single-language site, managing redirects is a linear spreadsheet task. On a site serving six locales with descriptive translated slugs (such as Polish, German, Norwegian, Spanish, and Portuguese URLs), the list quickly ran into thousands of rules. In October 2026 the Cloudflare Functions map holds 3619 rules and the _redirects file 249.

This volume exposed a hard platform ceiling: the _redirects file on Cloudflare Pages has an undocumented 100KB file-size limit. Past that threshold, the platform silently discards excess lines without throwing any build or deploy errors. As a result, earlier rules resolved correctly while later rules dropped into 404s. The fix was moving the entire redirect engine into an Edge middleware handler written in TypeScript (functions/redirect-map.ts), performing O(1) in-memory lookups at the request boundary.

#How to keep hreflang in sync across six languages

WordPress, with plugins like WPML or Polylang, conceals multilingual relationships behind database tables. A static SSG architecture exposes every relationship directly in files and markup.

Six language versions of every article must remain strictly parallel:

  • Identical technical H2 and H3 heading structure in matching logical order.
  • Complete bidirectional hreflang links in the document head pointing to all five language siblings.
  • Exact canonical URLs reflecting regional routing rules.
  • Consistent taxonomy tags across each language ecosystem.

When one locale drifts (such as adding an uncoordinated section or modifying a slug), search engines encountering asymmetrical hreflang tags start disregarding regional signals. This causes cross-market keyword cannibalization. To prevent this, I introduced automated cross-locale parity validation scripts run before every commit.

#Fixing JavaScript heap out of memory in Astro builds

Astro compiles static pages within a single worker process. As the repository expanded past 14,000 pages (15,718 in October 2026) (combining technical guides, architectural pillars, service landing pages, and local city hubs across 6 languages), the default V8 engine heap limit of 4GB was quickly exhausted.

Cloudflare Pages’ default build runner provides 8GB of RAM. During heavy TypeScript schema validation, MDX abstract syntax tree (AST) compilation, and Tailwind CSS tree-shaking, memory spikes triggered fatal JavaScript heap out of memory errors.

I resolved this by restructuring the entire deployment pipeline:

  1. Local Apple Silicon build execution: Builds run locally on M-series processors with an explicitly sized heap (--max-old-space-size, 16GB in the production profile). On 20 October 2026 the astro build step produced 15,718 pages in 5 minutes 30 seconds, and the Wrangler upload took another 338 seconds.
  2. Prerender artifact cleanup: During compilation, Astro outputs a dist/.prerender directory for server modules. Single bundle files reached 43.9 MiB, violating Cloudflare Pages’ 25 MiB single-file limit. The deploy script strips out these internal prerender chunks prior to upload.
  3. Direct Wrangler artifact deployment: The verified dist/ folder is uploaded directly to Cloudflare’s edge network, bypassing cloud runner memory limits completely.

#How to split a large Astro sitemap into multiple files

Another major hurdle was sitemap architecture. The standard @astrojs/sitemap integration does not know the indexability rules I keep per collection (noindex, length floors, city pages).

I built my own generator. In October 2026 it produces this layout:

  • A root sitemap-index.xml listing 24 sitemaps, four for each of the six locales.
  • Each locale has separate files for service pages (sitemap-pages.xml), the blog, the portfolio, and city pages (sitemap-locations.xml), 8180 URLs in total.
  • A strict filter eliminating all noindex routes (guaranteed by the automated check:noindex-sitemaps script).

This modular architecture allows search engines and AI crawlers to ingest sitemaps incrementally without timing out.

#Automated pre-deploy checks for a static Astro site

A quieter cost of leaving a dynamic CMS is rebuilding the automated safeguards that WordPress and its plugin ecosystem handled behind the scenes. WordPress prevented duplicate slugs, maintained database integrity, and checked broken links during editing. In a static repository, any frontmatter typo or broken link ships directly to production unless intercepted by automated CI testing.

By May 2026 I had 34 automated validation gates (run-gates.mjs); in October 2026 npm run gates lists more than 120. Examples:

  • Internal link integrity (check:incoming-internal-links and check:service-navigation-parity): Checks that every service receives inbound internal links and that navigation matches across all locales.
  • Diacritic and charset consistency (check:diacritic-wordlist): Catches character encoding regressions across Polish, German, Norwegian, Portuguese, and Spanish text.
  • Commercial pricing guard (check:no-own-prices): Audits informational content to prevent unapproved rate quotes from leaking outside dedicated pricing pages.
  • Content Security Policy validation (check:csp-inline): Scans HTML output to compute SHA-256 hashes for all inline scripts, enforcing strict CSP headers without unsafe-inline.
  • Editorial AI rhetoric audit (check:slop-rhetoric): Flags generic filler phrases, keeping content technically dense and actionable.

Each of these gates was written after a specific defect had already reached production.

#Image optimization and self-hosted fonts in Astro

In dynamic WordPress setups, image resizing is handled on upload via GD or ImageMagick. In Astro, image optimization becomes a core build-time operation.

Passing thousands of high-resolution images through Astro’s image component initially caused severe build slowdowns and Sharp memory exhaustion. I introduced strict asset separation:

  1. Pre-compressed static assets: Large backgrounds and decorative graphics are pre-converted to AVIF and WebP in public/, serving directly from the CDN without compiler overhead.
  2. Deterministic dimensions for article graphics: In-content images require explicit width and height attributes, which prevents layout shift (Cumulative Layout Shift).
  3. Self-hosted font subsets: I removed all external calls to Google Fonts. Geist is served from my own domain as WOFF2 files with a Latin subset (the @fontsource/geist package), eliminating render-blocking requests and ensuring full GDPR compliance.

#How to add JavaScript to forms on a static Astro site

Astro’s core advantage is shipping zero client-side JavaScript by default. Readers browsing technical articles download purely static HTML and CSS.

Where client interactivity is mandatory (the contact form, search, calculators), I add a small script on that page only:

  • The contact form is plain HTML with a short script inside the component, with no browser-side framework.
  • Spam protection is handled by Cloudflare Turnstile, without image CAPTCHA puzzles.
  • Submissions go to an endpoint in Cloudflare Pages Functions (functions/api/enquiry.ts), which sends the message through Resend and falls back to Web3Forms if Resend fails. There is no application server to maintain.

#Adding search to a static Astro site

WordPress traditionally relied on SQL queries against wp_posts for search functionality. In a static architecture, search must operate without a live database.

Instead of database queries, search uses an index generated at build time:

  • Each locale gets its own JSON file (/api/search/<lang>.json). The split by locale was necessary because a single shared file exceeded Cloudflare Pages’ 25 MiB per-file limit.
  • The search page downloads the index for its locale and searches it in the browser with Fuse.js. Each file is 7.7 to 9.4 MB today, so only people who open the search page download it, not every reader.
  • The server does no work for search, however many people use it. The cost is index size, which grows with the blog.

#How to measure Core Web Vitals after a WordPress to Astro migration

In WordPress environments, Core Web Vitals often degrade silently after plugin updates. In Astro, I keep an eye on performance like this:

  • Lighthouse in GitHub Actions: I run the lighthouse.yml workflow manually or through a pull request label when templates or layout change. It does not run on every commit, because Actions minutes are limited.
  • PageSpeed Insights measurement: on 20 October 2026 the lab mobile score was 94 for /pl/ (LCP 2.7 s) and 98 for /en/astro-developer/ (LCP 2.3 s). Google shows no CrUX field data for these URLs yet, so this is a lab measurement, not a Core Web Vitals assessment.
  • Instant edge cache purging: Upon artifact deployment, the release script invokes the Cloudflare Zone API (purge_cache: {"purge_everything": true}), ensuring global edge nodes instantly serve updated content without waiting for s-maxage expiration.
  • Deterministic immutable caching for hashed assets: Static CSS, JS bundles, and processed AVIF images are served with Cache-Control: public, max-age=31536000, immutable, while HTML documents carry Cache-Control: public, max-age=0, must-revalidate paired with edge caching tags. This allows instantaneous rollbacks and zero stale cache artifacts across global POPs.

#WordPress to Astro migration checklist

Drawing on this migration, here is the technical checklist required before switching DNS records:

  1. Complete route inventory: Extract every historical URL from WordPress MySQL tables (posts, pages, archives, categories, feeds, and attachments).
  2. Redirect architecture design: Construct an exhaustive 301 redirect map accounting for multilingual slug translations.
  3. Edge function deployment: Implement the redirect engine inside an Edge Function (Cloudflare Functions / Worker) to bypass static file caps.
  4. Hreflang mesh validation: Verify that all language versions include full reciprocal alternate links across the entire corpus.
  5. JSON-LD schema audit: Validate Article, FAQPage, HowTo, and Organization schemas against schema.org specifications.
  6. Modular sitemap deployment: Generate segmented sitemap files with strict exclusion of noindex pages.
  7. Heap and build profiling: Test build compilation under restricted memory and optimize V8 heap limits.
  8. Security header configuration: Enforce CSP, HSTS, X-Frame-Options, and Permissions-Policy at the edge CDN level.
  9. Indexability consistency audit: Ensure staging environments, technical pages, and duplicates are excluded from indexing.
  10. Post-cutover smoke testing: Configure automated HTTP smoke tests to monitor status codes and index health in Search Console immediately following DNS propagation.

#Should I move from WordPress to a static site after PHP2Shell?

On 21 October 2026 the PHP project released fixes for PHP2Shell, two critical flaws in PHP itself (CVE-2026-109618 and CVE-2026-109619). According to php.net, every PHP installation serving web requests is at risk, and the highest-risk setup has a demonstrated exploit that fires before any application code runs (php.net). Wiz Research adds that even a simple “Hello World” application on an affected PHP version can be vulnerable (Wiz). A site built into HTML files and served without PHP falls outside that notice, and this site is one of them: there was no PHP here to update that week. What to do on a site that still runs PHP is in my PHP2Shell guide.

On r/webdev the same week, the author of a technical publication who moved from WordPress to Hugo on Cloudflare Pages named the security gain:

Having a well-known /wp-admin/ portal exposed and probed, and then having to worry about underlying stack vulnerabilities, is, to me, the biggest win.

(u/Select_From, r/webdev)

In a second thread, a developer wrote that after PHP2Shell he had converted 45 client WordPress sites to Astro in 36 hours with an AI coding agent. One of the first replies asked about the part a static build leaves open:

You keep mentioning static files in your Github Readme, but what about new posts? What are you using that will allow clients to add new data or edit existing data?

(u/Lower_Rabbit_5412, r/webdev)

The author answered that he had built a git-based CMS for the 2 or 3 clients who wanted to edit their sites. The PHP attack surface goes away, and every dynamic job that WordPress did in one place moves somewhere else, with its own owner:

What WordPress handledWhere it goes on a static site
Editing in wp-adminMarkdown in Git, or a git-based or headless CMS
Contact formsa serverless function or a form service (here: Cloudflare Pages Functions with Turnstile)
Searchan index built at deploy time (here: one JSON file per locale, searched in the browser)
Plugins for a shop, bookings or membershipsa SaaS product, custom code, or a platform such as Shopify
Old URLsa 301 map covering every address

A reply in the Hugo thread put the order of trouble the same way I met it:

biggest hurdle is usually permalinks, every old URL needs a 301 or you bleed rankings. then the dynamic bits: comments, search, forms.

(u/ResidentPomegranate6, r/webdev)

My template port took weeks, and the months after it went into redirects, hreflang and the checks described above. A migration means rebuilding the site.

Patching and maintenance is often the better answer. PHP2Shell was closed by a PHP update, and a migration that takes weeks does nothing for a server that is unpatched today. Keep WordPress, WooCommerce, Magento or another PHP stack and keep it updated when editors work in the admin every day, when the business depends on plugins with real logic (shop, bookings, memberships, courses), or when nobody on the team will own a Git workflow. A static build fits when content changes rarely, few people edit it, and the dynamic parts come down to a form and a search box. A PHP site needs regular WordPress maintenance that includes PHP updates; a static rebuild is the job of an Astro developer.

Update , a middle path: staying on WordPress and rebuilding in Astro are not the only two options. Matt Cromwell described on 14 October 2026 how he rebuilt his company site without a public WordPress installation. WordPress runs only on his own machine, in WordPress Studio; a compiler turns his design system into theme.json and reusable block patterns; the Simply Static plugin exports the site to static files; and Cloudflare Pages serves them. He keeps the block editor, and the public site has no PHP to patch. Forms are where it costs work: a WordPress form plugin that submits over PHP cannot run on exported pages, so he wrote a separate endpoint that sends newsletter signups to MailerLite through its API. He also writes that “there are limits to what a static site in general can do”. It fits a marketing site edited on one machine. A shop, a members area or a team editing every day still needs a live server; when the editing side has to stay online but the front end should not run PHP, that is a headless CMS setup.

#What does a WordPress to Astro migration deliver after ten months?

Ten months in, the balance of the migration to Astro on Cloudflare Pages is positive for my content and services platform:

  1. Server response time (TTFB): A page served from Cloudflare’s edge cache answers from Gdynia in about 0.21 to 0.26 s, TLS handshake included (curl, 20 October 2026). I have no comparable measurement from the WordPress era, so I do not claim a percentage improvement.
  2. Smaller attack surface: Without PHP, an SQL database, and wp-admin, whole classes of WordPress attacks disappear: SQL injection, code execution through a vulnerable plugin, brute-force login attempts. A smaller surface remains to watch: the Cloudflare Functions endpoints (form, API) and the security headers.
  3. AI and search engine accessibility: Clean, semantic HTML without heavy JavaScript payloads allows search engines and LLM crawlers (OpenAI, Anthropic, Perplexity) to index technical content instantaneously, maximizing GEO/AEO discovery.
  4. Zero infrastructure scaling costs: Serving static assets from edge CDNs costs pennies compared to scaling dedicated MySQL database clusters under traffic spikes.

The honest engineering verdict: migrating from WordPress to Astro is not a superficial frontend rewrite. It is a comprehensive replatforming project where template porting is the easiest step, and the primary investment lies in routing architecture, automated validation gates, and multilingual governance. For content sites that care about speed and reach in several languages, the work pays off. If you want this architecture implemented for your team, see how I work as an Astro developer, or explore my WordPress to Astro migration service. More architectural case studies are available on the WPPoland engineering blog.

Next step

Turn the article into an actual implementation

This block strengthens internal linking and gives readers the most relevant next move instead of leaving them at a dead end.

Want this implemented on your site?

If you are planning a Headless WordPress setup, frontend decoupling, or migration to Astro, I can design and build the architecture, API, and frontend.

Explore other WordPress services and knowledge base

Strengthen your business with professional technical support in key areas of the WordPress ecosystem.

Frequently asked questions

Practical answers to apply the topic in real execution.

How long does a WordPress to Astro migration actually take?#
The initial port (templates, content export, a working build) is a matter of weeks for a site of moderate size. The full migration, to the point where search performance is stable and nothing regressed, has been running here since December 2025 and still needs fixes ten months in. The long tail is not the port; it is the redirect map, hreflang across locales, parity between language versions, and scaling the build. Budget for the tail, not the port.
Why move off WordPress at all if it works?#
The trade is dynamic convenience for static performance and control. WordPress renders pages on request and gives you an admin and a plugin ecosystem; a static Astro build renders every page ahead of time and serves files from the edge, which is faster and has a smaller attack surface, at the cost of owning the routing and build yourself. It is worth it for a content site where speed, stability, and AI-crawler access matter more than in-dashboard editing convenience. It is not worth it for a site that lives on dynamic, logged-in functionality.
What was the hardest part of the migration?#
Not the templates. The redirect layer and locale parity. Every previously indexed WordPress URL needs a 301 to its new address, and on a multilingual site that list runs into thousands of rules, which collided with a Cloudflare Pages file-size cap. Keeping six language versions structurally identical (same sections, aligned hreflang, matching canonical URLs) is continuous work, not a one-time task.
Can Cloudflare Pages build a large Astro site?#
Serve it, easily. Build it, not past a certain size. Cloudflare's own Pages build runner has an 8GB memory ceiling, and a large multilingual Astro site with thousands of prerendered pages needs more heap than that to build. The fix was to build locally with a 16GB heap and deploy the finished artifact with Wrangler, rather than rely on the platform's build step.
Do images need special handling in Astro?#
Yes. Images imported through Astro's asset pipeline are optimized at build time, which is excellent for output quality but adds to build memory and time, and very large source images can push the build into an out-of-memory failure. The rule that held: pre- optimized, served-as-is background images go in the public directory; images that genuinely benefit from pipeline processing stay in the asset folder, kept reasonably sized.

Need an FAQ tailored to your industry and market? I can build one aligned with your business goals.

Let’s discuss

Related Articles

Cloudflare Workers and WordPress: serving WooCommerce at the edge

Cloudflare Workers runs JavaScript and WebAssembly at hundreds of data centres in 100+ countries worldwide. Pairing Workers with a WordPress origin moves the read path off the WordPress server and turns WooCommerce into an edge-rendered store. Here is how the architecture works, where it breaks, and what to measure before adoption.