# Migrating WordPress to Astro Without Losing a URL: the Same Blog, Only Faster

The GSMX blog moved from WordPress to static Astro and, for readers, only the speed changed: the same addresses, posts, ads and the forum next door. How I did it and how I checked it.

**Canonical:** https://spoko.space/blog/wordpress-to-astro-migration-gsmx/  
**Language:** en  
**Published:** 2026-11-07  
**Tags:** WordPress, Astro, migration, Cloudflare Workers, Core Web Vitals, SEO  
**Category:** Engineering

---
**[The GSMX blog](https://gsmx.pl/blog/) moved from WordPress to static Astro and, for readers, only the speed changed.** The addresses, posts and ads are the same, and the forum next door works as before. I checked 2,299 old addresses and none of them leads to the wrong place. On mobile, PageSpeed Insights went from 33–85 to 99–100 (median of 3 runs), and the server answers in under 60 ms instead of over a second.

In short:

- **Same address.** The blog stayed at `gsmx.pl/blog/`. A Cloudflare Worker bound to that path alone serves it, so the forum never noticed.
- **No address was lost.** Posts kept their slugs exactly. Only what was broken changed, and every such change has a 301 redirect.
- **Before and after, measured the same way.** The same 5 addresses, the same Lighthouse and the median of 3 runs. Without that the comparison means nothing.
- **The ads stayed.** They are now the heaviest part of the page, not the blog.
- **Going back was always one command away.** WordPress stayed on the hosting for a 4-week observation period.

## The starting point: WordPress next to a forum {#the-starting-point-wordpress-next-to-a-forum}

gsmx.pl is first of all a forum, which runs at the root of the domain. The GSM news blog lived in the `/blog/` directory, on a separate WordPress install, with posts going back to 2006.

The state on 25 October 2026, from production and from a copy of the database:

- **26 installed plugins, 20 of them active:** Jetpack, Rank Math, Quick AdSense Reloaded, WP Hide, Fast Velocity Minify, Sucuri, Disqus Conditional Load, an SEO auto-linker and more. On top of that the Presso theme with a child theme, and three sets of tables in the database left over from old installs.
- **33–42 scripts and 8–10 stylesheets per page.** The HTML went through Cloudflare uncached, and the server answered in 0.8–1.6 s.
- **Posts weighed 14–17 MiB** according to PageSpeed Insights. Most of that weight was video ads loaded on every post.
- **20 years of content:** several hundred posts, 705 tags, Flash embeds, images on tinypic and fotosik, which no longer exist, galleries, tables and styles written into the HTML.

Search traffic was negligible: **16 clicks in 16 months**. That matters for the risk. The migration could not hurt SEO that was barely there, and I am not promising more traffic here. The reasons were different. I wanted a site with no PHP and no database, so nothing to patch. I wanted to be able to move it to any static host. And I wanted to publish new posts from the repository, without logging into a panel.

## Measure "before" first {#measure-before-first}

Before changing anything, I measured production. An "after" measurement only means something if you repeat exactly the same thing, so I fixed the method at the start and kept it to the end:

- **5 addresses:** the blog home page, a category and three posts from 2026, 2010 and 2006. The old posts carry the most old HTML.
- **Lighthouse 12 locally, 3 runs per address and mode, median.** A single run lies. In PageSpeed Insights two posts with almost identical HTML scored 85 and 33.
- **PageSpeed Insights and GTmetrix** as public, verifiable snapshots.
- **The full list of addresses** from the WordPress REST API: posts, pages, categories, tags, authors, media. That list later became the redirect test.

![PageSpeed Insights for a 2006 post on WordPress, mobile: performance 33, Total Blocking Time 7,690 ms, Speed Index 21.9 s, no real-user data](https://spoko.space/_spoko/gsmx-wordpress-psi-przed.BdhLh0iC.webp "A 2006 post on WordPress in PageSpeed Insights, 25 October 2026 (Polish interface)")

One thing could not be measured. Google had no real-user data for the blog (CrUX: "No data" for all 5 addresses), because traffic was too low. So the comparison is a lab comparison, and I describe it as one.

## What I considered and dropped {#what-i-considered-and-dropped}

| Option | Why not |
| --- | --- |
| A blog.gsmx.pl subdomain | It would need a redirect for every address from 20 years. Keeping the address was possible, so there was no reason to change it. |
| A copy of the Presso theme in Astro | It would pull in the same libraries that slowed WordPress down. The blog got a new, simple layout with the GSMX logo and colours. |
| A custom CMS with an admin panel | Too heavy for a one-language blog whose authors are technical and do not need a panel. It needs PHP and a database, which is exactly what I wanted to get rid of. |
| Just caching and a plugin clean-up | It would make the site faster, but PHP, the database and the updates would stay. And there would be no publishing from the repository. |

## How it works: a Worker on /blog only {#how-it-works-a-worker-on-blog-only}

The whole gsmx.pl domain goes through Cloudflare. The blog is handled by a Worker with Static Assets, bound to two routes: `gsmx.pl/blog` and `gsmx.pl/blog/*`. Everything else reaches the forum server, as before the migration.

**What happens to a request to gsmx.pl**

1. `gsmx.pl/blog` and `gsmx.pl/blog/*` (the blog Worker on Cloudflare) → the rest of the domain (the PHP forum, unchanged)
2. **The _redirects file** — 1,466 301 rules from old addresses, generated by the converter.
3. **A finished file from dist/** — The address `/blog/slug/` is the file `dist/blog/slug/index.html`, with no code run.
4. **Worker code, only where a file is not enough** — old `?p=` addresses; 410 for `wp-admin`, `xmlrpc.php` and removed tags; date archives, RSS, old thumbnails

- URL contract test: **2,299 old addresses, 0 failures** — 200 for a post, 301 to the right place, 410 for a removed one.

A `gsmx.pl/blog*` route looks simpler, but it would also catch forum addresses that start with "blog", such as `/blogi/`. Hence two exact routes, and the post-switch test checked the forum as well.

Astro builds the blog with `base: '/blog'` into `dist/blog/`, and the Worker serves the whole of `dist/`. The address `/blog/slug/` then maps to the file `dist/blog/slug/index.html`, and Cloudflare returns it without running any code. The Worker code only runs where a file is not enough:

- **old addresses with parameters**, such as `/blog/?p=1234`, are redirected to the right post;
- **410 Gone** for `wp-admin`, `wp-login.php`, `xmlrpc.php`, `wp-json` and removed tags, so Google knows it is deliberate and not an outage;
- **date archives, RSS and old thumbnail paths**, which a static rule cannot describe.

The other redirects, 1,466 rules, live in the `_redirects` file. The converter generates it; nobody writes it by hand. That file has to sit at the root of `dist/`, not in `dist/blog/`, because that is the only place Cloudflare reads it. In `dist/blog/` it would just be a downloadable file.

Cloudflare redirects addresses without a trailing slash (`/blog/slug`) with a 307, not a 301 as WordPress did. I accepted that on purpose: the canonical addresses have the slash, and the versions without it barely appeared in links or in Search Console.

## The converter: content, media and 20 years of leftovers {#the-converter-content-media-and-20-years-of-leftovers}

The converter reads post content from the WordPress REST API (`content.rendered`) and the rest from a local copy of the database. Rendered content already has the shortcodes, galleries and paragraphs expanded, which is what the reader saw. Rebuilding that from the raw content in the database would be a large, fragile piece of code for several hundred posts. From the database I take what REST does not give: Rank Math meta titles and descriptions, relations and the media map.

The old posts stayed as cleaned-up HTML inside Markdown files, so they look the way they did. New posts are written in plain Markdown.

What the converter did along the way, according to the migration report:

- **13 YouTube embeds from the Flash era** became a lightweight player that loads on click. 4 other Flash embeds became a note with a link.
- **It checked every image on a third-party host.** 21 still worked, so it downloaded them onto the blog. 152 images in 53 posts pointed at services that are gone (tinypic, fotosik, imageshack and old domains). It removed them from the content and listed them in the report.
- **It prepared the media:** 508 files, originals up to 1,600 px and WebP variants with `srcset`, `width` and `height`, so images do not push the text around.
- **It fixed what was broken:** 3 slugs with typographic dashes and quotes percent-encoded in the address, subcategories reachable at two addresses, a category called `bada-2`, 7 pairs of duplicate tags (`wi-fi` and `wifi`, `app-store` and `appstore`…). Every change got a 301, and the converter rewrote internal links in the content to the new addresses straight away.

Working, indexed addresses stayed, even when they were not pretty. Changing an address has a cost, and a prettier slug brings nothing.

## The URL contract test: 2,299 addresses, 0 failures {#the-url-contract-test-2299-addresses-0-failures}

The hard part of a migration is not the templates, it is the old addresses. So the full list of WordPress addresses became a script that checks each one against the contract: 200 for a post, 301 to the right place for a changed address, 410 for a removed one.

I ran it on staging before the switch-over and on production right after it:

| Address source | Addresses | Failed |
| --- | --: | --: |
| Posts from the REST API | 336 | 0 |
| Tags from the REST API | 705 | 0 |
| Old `?p=` addresses | 707 | 0 |
| Media (WP Hide and `wp-content` paths) | 300 | 0 |
| Thumbnails | 194 | 0 |
| Authors, categories, pages | 33 | 0 |
| Other: RSS, date archives, sitemaps, `ads.txt`, GSC verification | 24 | 0 |
| **Total** | **2,299** | **0** |

That was the condition for the switch-over: 100% or no deploy.

## A switch-over without risk {#a-switch-over-without-risk}

Before binding the Worker to production, I checked the Cloudflare zone. Redirect Rules and Page Rules run before a Worker, so one old rule on `/blog` would take the traffic. Rocket Loader or email obfuscation would change the HTML. Staging was in the same zone, so I compared its response with the file in `dist/`. The only difference was the Web Analytics script.

The switch-over itself was a `wrangler deploy` with two routes, on 2 November 2026 at 08:02 UTC. WordPress was not shut down. It stayed on the hosting; traffic to `/blog` simply stopped reaching it. Going back means removing those two routes and takes seconds.

**From plan to switch-over**

1. **25 Oct: plan and "before"** — The migration spec, Lighthouse, PageSpeed Insights and GTmetrix on 5 addresses, the full address list.
2. **25 Oct: converter and Astro site** — Content from the REST API, media, the redirect map, the new blog layout.
3. **25–27 Oct: Worker on staging** — staging.gsmx.pl, the URL contract test, PageSpeed Insights on staging.
4. **30 Oct: categories and descriptions** — A new category structure, separate fields for the H1, the SEO title and the descriptions.
5. **2 Nov: switch-over** — Worker routes on production, the 2,299-address test, sitemaps in Search Console.
6. **2 Nov: "after"** — The same script, the same addresses, the same location as 8 days earlier.
7. **7 Nov: PageSpeed Insights and GTmetrix** — Public measurements on production, three ad fixes and 3 PageSpeed Insights runs.

## Results: before and after {#results-before-and-after}

The same method as before the migration: Lighthouse 12 locally, 3 runs, median. The migrated site carries AdSense, just like WordPress did.

| Page | Mobile | Desktop | LCP (mobile) | CLS (mobile) | Server response | Requests | Scripts | Weight |
| --- | --: | --: | --: | --: | --: | --: | --: | --: |
| Home page | 36 → **94** | 80 → **100** | 11.08 → 2.21 s | 0.335 → 0.039 | 1,518 → 59 ms | 83 → 28 | 36 → 5 | 2,433 → 709 KB |
| Category | 61 → **93** | 91 → **100** | 5.79 → 2.20 s | 0.063 → 0.039 | 1,407 → 48 ms | 87 → 28 | 38 → 5 | 1,315 → 717 KB |
| 2026 post | 68 → **86** | 91 → **100** | 4.93 → 1.89 s | 0.075 → 0.042 | 1,059 → 46 ms | 74 → 21 | 42 → 5 | 1,150 → 505 KB |
| 2010 post | 60 → **89** | 89 → **100** | 6.66 → 2.21 s | 0.136 → 0.051 | 1,222 → 46 ms | 78 → 20 | 42 → 5 | 1,730 → 474 KB |
| 2006 post | 49 → **87** | 89 → **100** | 6.77 → 1.74 s | 0.155 → 0.045 | 1,609 → 26 ms | 73 → 20 | 42 → 5 | 1,581 → 474 KB |

Server response changed the most. WordPress assembled every page in PHP on every request. Now Cloudflare returns a finished file from the nearest server. In this measurement all 5 pages score 100 on desktop.

**Server response time before and after the migration**

| Page | Version | Response time |
| --- | --- | --- |
| Home page | WordPress, 25 Oct | 1,518 ms |
| Home page | Astro, 2 Nov | 59 ms |
| Category | WordPress, 25 Oct | 1,407 ms |
| Category | Astro, 2 Nov | 48 ms |
| 2026 post | WordPress, 25 Oct | 1,059 ms |
| 2026 post | Astro, 2 Nov | 46 ms |
| 2010 post | WordPress, 25 Oct | 1,222 ms |
| 2010 post | Astro, 2 Nov | 46 ms |
| 2006 post | WordPress, 25 Oct | 1,609 ms |
| 2006 post | Astro, 2 Nov | 26 ms |

Lower is better.

### PageSpeed Insights and GTmetrix {#pagespeed-insights-and-gtmetrix}

A Lighthouse run on my own machine is easy to doubt, so I repeated the public measurements too: PageSpeed Insights on the same 5 addresses, mobile and desktop, plus GTmetrix from Seattle. Before the migration I ran PageSpeed Insights once per address. After it I ran three independent runs and report the median, because a single run can be a dozen points off. Every result links to a report.

| Page | Mobile | Desktop | Weight (mobile) |
| --- | --: | --: | --: |
| [Home page](https://pagespeed.web.dev/analysis/https-gsmx-pl-blog/qawizwbss0?form_factor=mobile) | 51 → **99** | 98 → **100** | 2,761 → 536 KiB |
| [Category](https://pagespeed.web.dev/analysis/https-gsmx-pl-blog-kategoria-telefony/9j40n6i38j?form_factor=mobile) | 62 → **100** | 85 → **99** | 2,005 → 519 KiB |
| [2026 post](https://pagespeed.web.dev/analysis/https-gsmx-pl-blog-odkryj-najlepsze-aplikacje-na-androida/2n27lenukb?form_factor=mobile) | 52 → **99** | 61 → **96** | 15,759 → 620 KiB |
| [2010 post](https://pagespeed.web.dev/analysis/https-gsmx-pl-blog-afera-prototypowa-czyli-iphone-z-san-jose/0q73umjmgf?form_factor=mobile) | 85 → **100** | 92 → **100** | 14,359 → 618 KiB |
| [2006 post](https://pagespeed.web.dev/analysis/https-gsmx-pl-blog-zdejmowanie-simlocka-ze-starszych-modeli-telefonow/prwqdy7np3?form_factor=mobile) | 33 → **99** | 62 → **99** | 16,945 → 515 KiB |

Before the migration: 25 October 2026, a single run, Lighthouse 13.5. After the migration: 7 November 2026, median of 3 runs, Lighthouse 13.6.

Across all 30 runs after the migration, mobile scores 97–100 and desktop 95–100. On WordPress it was 33–85 and 61–98. The 2006 post had a Total Blocking Time of 7,690 ms and now has 40–160 ms. Posts weigh about 0.5–0.6 MiB instead of 14–17 MiB. Accessibility and SEO score 100, against 79–96 and 91–92 on WordPress.

**Mobile PageSpeed Insights before and after the migration**

| Page | Version | Score |
| --- | --- | --- |
| Home page | WordPress, 25 Oct | 51 |
| Home page | Astro, 7 Nov | 99 |
| Category | WordPress, 25 Oct | 62 |
| Category | Astro, 7 Nov | 100 |
| 2026 post | WordPress, 25 Oct | 52 |
| 2026 post | Astro, 7 Nov | 99 |
| 2010 post | WordPress, 25 Oct | 85 |
| 2010 post | Astro, 7 Nov | 100 |
| 2006 post | WordPress, 25 Oct | 33 |
| 2006 post | Astro, 7 Nov | 99 |

Higher is better; 100 is the maximum.

**Mobile page weight before and after the migration**

| Page | Version | Weight |
| --- | --- | --- |
| Home page | WordPress, 25 Oct | 2,761 KiB |
| Home page | Astro, 7 Nov | 536 KiB |
| Category | WordPress, 25 Oct | 2,005 KiB |
| Category | Astro, 7 Nov | 519 KiB |
| 2026 post | WordPress, 25 Oct | 15,759 KiB |
| 2026 post | Astro, 7 Nov | 620 KiB |
| 2010 post | WordPress, 25 Oct | 14,359 KiB |
| 2010 post | Astro, 7 Nov | 618 KiB |
| 2006 post | WordPress, 25 Oct | 16,945 KiB |
| 2006 post | Astro, 7 Nov | 515 KiB |

Lower is better.

### What the first measurement caught {#what-the-first-measurement-caught}

The first PageSpeed Insights run after the migration was less even. The home page scored 90 on desktop, and two old posts had a CLS of 0.084 and 0.113. The local Lighthouse run had not shown it. Every time the culprit was the in-content ad slot that AdSense left unfilled. From Poland the ad usually shows; PageSpeed Insights measures from the US and often gets none.

There were three causes, and I found them one after another:

1. The empty ad slot disappeared at once and the text under it jumped about 300 px. Now it disappears only while it is below the screen.
2. AdSense first gives the ad 280 px and, when there is none, takes it out of the page layout. A slot reserved at 250 px fell back to that height and the paragraph under it moved up 30 px. The reservation is now 280 px.
3. An empty ad gets a width of 0 from AdSense. Centred, it moved its contents 330 px to the right. It is now left-aligned.

After these fixes desktop CLS is at most 0.011, and the home page scores 100 on all three runs. The general lesson: a static page does not move on its own. What moves it is whatever you load on top of it from elsewhere, and that has to be measured from more than one place, not just from your own machine.

GTmetrix (Seattle, desktop, home page): grade **C → A**, performance **62% → 90%**, structure 89% → 99%, LCP 3.3 s → 0.38 s. Reports: [before](https://gtmetrix.com/reports/gsmx.pl/FfaUrOUo/) and [after](https://gtmetrix.com/reports/gsmx.pl/USRyZHjb/). That GTmetrix run predates the ad fixes.

![A 2008 post on the new GSMX blog on a phone: the header with the GSMX logo, the Rynek (market) category, the Polish title about LG and Samsung in a patent suit, the date, the cover and the start of the post](https://spoko.space/_spoko/gsmx-astro-wpis-telefon.CWlf0rD5.webp "A 2008 post after the migration, on a phone")

## What is left: the ads {#what-is-left-the-ads}

On mobile the score stops short of 100 because of the ads, not the blog. On staging, PageSpeed Insights broke down the CPU time on the 2026 post:

- Google ad scripts: **4.2 s**;
- the ad consent dialog: **1.4 s**;
- the blog's own JavaScript: **41 ms**.

When an ad manages to load during the test, the score drops (that post on staging: 69). When it does not, the score is 99–100. There is no fixing that without removing the ads, and the ads stay, because they pay for the blog.

Two ad-related issues only showed up on production:

- **The first "after" run measured CLS of 0.155–0.215 on short old posts.** The in-content ad slot only got its height once the ad loaded, and pushed the paragraph below it down. Now every slot reserves 250 px from the first render, and CLS dropped to 0.04–0.05. What remains comes from Google's consent dialog, not from the blog's code.
- **AdSense Auto ads placed themselves wherever they liked.** I turned them off for the whole of `/blog/`, and the blog has 4 ad units of its own in fixed places. The forum keeps Auto ads as before. The video ads, which weighed a dozen-plus MiB per post, went away together with WordPress.

## What I do not know {#what-i-do-not-know}

The comparison is a lab comparison. On WordPress, Cloudflare Web Analytics collected only 4 measurements from the blog before the switch-over, so I have no real-user "before". Nor do I have proof that a faster blog will get more search traffic. At 16 clicks in 16 months any change would be noise. The observation period runs until the end of November; after that WordPress will be removed from the hosting.

## After the migration: posts from the repository {#after-the-migration-posts-from-the-repository}

A static blog does not mean a blog without new posts. A post is a Markdown file in the repository. On gsmx.pl new topics are collected from RSS feeds, and the same automation drafts the post with sources and a cover and opens a pull request. Before the merge every draft goes through a fact check against its sources, and then the blog is built and shipped to Cloudflare with `wrangler deploy`. 15 posts went out that way in the first week after the migration. A post dated in the future waits and appears with the first build after that date.

**How a new post gets published**

1. **Topic** — Automation collects topics from RSS feeds, or an author writes the post themselves.
2. **Pull request** — The post as a Markdown file, with sources and a cover.
3. **Review** — facts checked against the sources; copy and publish date
4. **Merge and deploy** — Astro build and `wrangler deploy` to Cloudflare.

- Published: **The post on gsmx.pl/blog** — No panel, no database, no PHP.

## What comes next: the forum {#what-comes-next-the-forum}

The forum on gsmx.pl still runs on XenForo, which means PHP. I am testing a few approaches for it as proofs of concept and MVPs. Most recently I moved it to Flarum as a test. Flarum's front end is built on jQuery and a fairly dated approach, though, so Flarum will most likely stay only as the back end (headless), with Astro and Vue taking over the forum's pages. More on that soon.

## When a migration like this makes sense {#when-a-migration-like-this-makes-sense}

Static Astro fits when content changes at publishing time rather than on every visit. That is true of a blog, a news site and a company website. The number of authors does not matter. On WordPress the GSMX blog had 17 of them, and all of them were moved over. Now anyone with access to the repository can be an author: they write a post as a Markdown file in an editor such as VS Code or with an AI assistant, and open a pull request. On GSMX every author is technical, so nobody misses the panel.

When the authors are not technical and want to keep a panel, WordPress can stay as the CMS while Astro only renders the content. That is how [polo.blue](https://polo.blue/) has run since 2023. I describe both options on the [WordPress to Astro migration](https://spoko.space/wordpress-to-astro/) page.

If your WordPress is slow and the plugin list grows with every update, start with a measurement and a list of addresses, as here. Those two things decide whether a migration succeeds. The templates are the easy part.

## Frequently Asked Questions {#faq}

### Do the old addresses still work after moving from WordPress to Astro? {#faq-do-the-old-addresses-still-work-after-moving-from-wordpress-to-astro}

Yes, if you plan for it from the start. On gsmx.pl every post kept its address exactly, and every address that did change (3 slugs with characters percent-encoded in the URL, categories, duplicate tags, media paths) got a 301 redirect. A script checked 2,299 old addresses before the switch-over and right after it. None failed.

### Can an Astro blog live under /blog/ next to another application on the same domain? {#faq-can-an-astro-blog-live-under-blog-next-to-another-application-on-the-same-domain}

Yes. On gsmx.pl the root of the domain is a PHP forum, and the blog is served by a Cloudflare Worker with Static Assets bound to the routes <code>gsmx.pl/blog</code> and <code>gsmx.pl/blog/*</code>. Everything else goes to the forum server as before. A <code>/blog*</code> route would be a mistake, because it would also catch forum addresses that start with "blog".

### How much faster is a static blog than WordPress? {#faq-how-much-faster-is-a-static-blog-than-wordpress}

On the same 5 addresses, with the same tool and the median of 3 runs: mobile Lighthouse went from 36–68 to 86–94, desktop from 80–91 to 100, and server response time fell from 0.8–1.6 s to under 60 ms. In PageSpeed Insights the mobile score went from 33–85 to 97–100 across all runs, and GTmetrix raised the home page grade from C to A. Requests dropped from 73–87 to 20–28, scripts from 36–42 to 5.

### What happened to AdSense after the migration? {#faq-what-happened-to-adsense-after-the-migration}

The ads stayed, and they are now what limits the mobile score. On one post the ad scripts used 4.2 s of CPU and the consent dialog 1.4 s, while the blog's own JavaScript used 41 ms. Every ad slot reserves its height from the first render, because without that the ad pushed the text down (CLS up to 0.215).

### How long did the migration take? {#faq-how-long-did-the-migration-take}

8 days from plan to switch-over: the plan and the "before" measurement on 25 October 2026, the switch-over on 2 November. WordPress stayed on the hosting for a 4-week observation period, so going back meant removing two Worker routes.

### How do you publish without an admin panel? {#faq-how-do-you-publish-without-an-admin-panel}

A post is a Markdown file in the repository. On gsmx.pl new topics are collected from RSS feeds, the same automation drafts the post and opens a pull request with it, and after review and merge the blog is built and shipped to Cloudflare with <code>wrangler deploy</code>. 15 posts went out that way in the first week after the migration.


## Sources {#sources}

- [Cloudflare Workers: Static Assets](https://developers.cloudflare.com/workers/static-assets/) — Serving static files from a Worker, including run_worker_first.
- [Cloudflare Workers: redirects in Static Assets](https://developers.cloudflare.com/workers/static-assets/redirects/) — The format and rules of the _redirects file.
- [Cloudflare Workers: HTML handling](https://developers.cloudflare.com/workers/static-assets/routing/advanced/html-handling/) — The auto-trailing-slash mode and the redirect of addresses without a trailing slash.
- [Cloudflare Workers: Routes](https://developers.cloudflare.com/workers/configuration/routing/routes/) — Binding a Worker to paths in a zone where another application runs.
- [Astro configuration reference](https://docs.astro.build/en/reference/configuration-reference/) — base, trailingSlash, build.format and outDir, used to build the blog under /blog/.
- [WordPress REST API](https://developer.wordpress.org/rest-api/) — The source of the rendered post content (content.rendered) for the converter.
- [Pagefind](https://pagefind.app/) — The static search that replaced WordPress search.
- [Lighthouse](https://developer.chrome.com/docs/lighthouse/overview) — The tool behind the before and after measurements (median of 3 runs).
- [GTmetrix: gsmx.pl/blog before the migration (25 Oct 2026)](https://gtmetrix.com/reports/gsmx.pl/FfaUrOUo/) — WordPress, Seattle, desktop: grade C, performance 62%.
- [GTmetrix: gsmx.pl/blog after the migration (7 Nov 2026)](https://gtmetrix.com/reports/gsmx.pl/USRyZHjb/) — Astro, Seattle, desktop: grade A, performance 90%.
