Skip to content

#15 – Migrating from WordPress to AstroJS Starlight

· 10 min read · 1905 words

Migrating from WordPress to AstroJS Starlight - background

How I migrated my personal website from WordPress on a Polish hosting to an open-source AstroJS Starlight site on Cloudflare Pages — and why it was worth every commit.

Before (WordPress with 25 plugins):

Old homepage (WordPress)

After (Astro Starlight, open-source):

New homepage (Astro Starlight)

The site is no longer just a portfolio and blog — it’s now a digital garden with a knowledge base section. It starts with just a handful of pages, but I plan to expand and regularly update them over time. I wanted it to feel closer to my personal Obsidian vault: a place where notes evolve, interlink freely, and stay in plain Markdown. The sidebar, dark theme, and “Last updated” timestamps all echo that Obsidian aesthetic.

My WordPress site had been running for 8 years (since 2018) on a Polish shared hosting provider (MyDevil). It served me well, but over time I started feeling the friction:

  • 25 plugins to maintain — security updates, compatibility issues, bloat (some were paid)
  • Subscriptions for updates — my theme and some plugins required yearly subscriptions to keep receiving updates
  • No version control — changes were irreversible clicks in a web admin panel
  • Slow performance — shared hosting with PHP overhead
  • Vendor lock-in — content trapped in a database, not in files I own
  • PHP version dictated by hosting — limited control over the runtime environment
  • Cost — yearly hosting fees for what is essentially a static site

I wanted something that would give me full control over my content as plain Markdown files, version history through Git, free hosting, and lightning-fast performance. Part of what motivated me was my move from OneNote to Obsidian back in 2022. Since then, I’ve been writing and editing Markdown daily, both for personal notes and at work. WordPress felt increasingly out of place.

After some research, I landed on AstroJS Starlight deployed on Cloudflare Pages, with the source code open on GitHub. Since it’s open-source, I welcome collaborative suggestions — just please, no ads or tool promotions.

Before committing, I evaluated several static site generators and themes:

Part of what drew me to a documentation-style framework is that I genuinely enjoy writing and maintaining documentation at work. Starlight’s structure felt natural.

For hosting, Cloudflare Pages stood out as likely the best free option over Netlify and Vercel — generous limits, global edge network, and tight DNS integration.

Before diving in, I studied how others had built their personal sites, especially:

Each had a different approach, but the common thread was: static site, Markdown content (mostly, I believe), Git-backed, fast.

Before anything else, I installed Node.js (I use nvm to manage versions) and created a GitHub repository for the project. This gives you version control from day one.

WordPress has a built-in export tool under Tools → Export. I exported all content as an XML file, then used wordpress-export-to-markdown to convert it:

Terminal window
npm install -g wordpress-export-to-markdown
wordpress-export-to-markdown --input=export.xml --output=src/content/docs

Options I chose:

  • Put each post into its own folder? → No
  • Add date prefix to posts? → Yes
  • Organize posts into date folders? → No
  • Save images? → All Images

This gave me all my posts as .md files with frontmatter and images — a solid starting point. Unfortunately, this was just the beginning. The exported content required significant cleanup:

  • Converting WordPress shortcodes to standard Markdown links
  • Replacing remote wp-content image URLs with local assets
  • Standardising ~284 image filenames (lowercase, hyphens) for Linux case-sensitivity
  • Fixing quote formatting, highlight blocks, and image captions
  • Removing outdated pages and unused frontmatter fields

This turned into a massive PR that also added site infrastructure (RSS, LaTeX support, custom components) and fixed ~30 grammar issues across 11 files.

Creating the Starlight project was straightforward:

Terminal window
npm create astro@latest -- --template starlight

The template gives you a working site out of the box with a sidebar, search (via Pagefind), dark/light theme toggle, and sensible defaults. From there, I customised the sidebar structure, added my branding, and dropped in the exported content.

Here’s where it gets interesting. I used the Claude Code extension in VS Code (which later became GitHub Copilot with Claude) to assist with the bulk of the migration work. Having an AI pair programmer turned what could have been weeks of tedious work into a much more manageable process.

Claude helped with:

  • Fixing broken links and WordPress leftovers across all posts
  • Adding SEO infrastructure (Schema.org structured data, Open Graph tags, robots.txt)
  • Building custom components (page title with reading time, footer with JSON-LD)
  • Setting up tag pages and RSS feed
  • Creating URL redirects from old WordPress paths
  • Image optimisation and deduplication
  • Grammar review across all content

The full commit history on GitHub tells the story.

Deploying to Cloudflare Pages was remarkably simple:

  1. Connected the GitHub repository in the Cloudflare dashboard
  2. Set the build command to git fetch --unshallow && npm run build
    • The git fetch --unshallow is important — Cloudflare Pages does a shallow clone by default, which breaks Starlight’s “Last updated” dates (every page shows the deployment date instead of its actual last-modified date)
  3. Set NODE_VERSION to 22 and build output to dist
  4. Pointed my domain’s DNS to Cloudflare

That’s it. Every push to main triggers an automatic deployment. Every branch and commit gets its own preview URL, which makes testing changes much easier before merging. Rollbacks are one click away.

┌─────────┐ git push ┌─────────┐ webhook ┌──────────────────┐
│ VS Code │ ───────────────► │ GitHub │ ──────────────► │ Cloudflare Pages │
└─────────┘ └─────────┘ └────────┬─────────┘
npm run build
┌──────────┐ CDN ┌──────────────┐ static ┌─────────────────┐
│ Visitors │ ◄────────────── │ Edge Network │ ◄──────── │ Astro SSG │
└──────────┘ └──────────────┘ └─────────────────┘

After the core migration, I spent time on improvements that would have been painful or impossible on WordPress:

  • Custom 404 page with a themed dead link illustration
  • Tag system with an index page and individual tag pages
  • “Discuss on” links per post (linking to Facebook, X, LinkedIn discussions)
  • Responsive iframe wrapper for YouTube embeds via a custom remark plugin
  • Mobile header auto-hide on scroll via custom JavaScript and CSS
  • i18n customization to rename Starlight’s “On this page” heading
  • Broken link checker script (npm run check:links) — scans all Markdown files for URLs, then checks them concurrently via HTTP HEAD/GET and reports dead links grouped by file
  • Recommendations sync script (npm run sync:recommendations) — pulls the recommendations-for-engineers README from GitHub and transforms it into a Starlight-compatible page (converting admonitions, stripping TOC, fixing links)
  • Newsletter migration from Mailchimp to Substack
  • Donation link migration from PayPal to Ko-Fi
  • Yearly auto-rebuild via a Cloudflare deploy hook triggered by GitHub Actions (to keep the copyright year current)
  • Downtime monitoring with UptimeRobot — free checks every 5 minutes with email alerts
  • Knowledge base section — a growing digital garden with 16+ pages on topics from Kubernetes to music production

One concern before migrating was: what functionality would I lose? I went through all 25 WordPress plugins to check:

PluginNeeded?
Akismet Anti-spam❌ No comments system (yet)
Classic Editor / Gutenberg❌ Writing in Markdown now
Custom Highlight Color❌ Handled by CSS
Enlighter - Syntax Highlighter❌ Starlight uses Shiki with excellent syntax highlighting
Fixed TOC❌ Starlight has built-in table of contents
footnotes❌ Standard Markdown footnotes work
Forms for Mailchimp❌ Migrated to Substack
instant.page❌ Static site is already fast
Jetpack❌ Cloudflare provides analytics
Phoenix Media Rename❌ Files are just files in a Git repo
Post Reading Time Estimate❌ Custom PageTitle component calculates this
Redirection✅ Reimplemented as Astro redirects in config
WP-KaTeX❌ Using remark-math + rehype-katex
Yoast SEO❌ Custom Schema.org + Open Graph implementation
…and 11 others

Only Redirection required actual reimplementation — and it was just a config object in astro.config.mjs mapping old WordPress date-based URLs to new paths.

I ran Lighthouse audits on both sites to compare. The results were dramatic:

Lighthouse score — WordPress on shared hosting (before)

And after the migration:

Lighthouse score — Astro on Cloudflare Pages (after)

The difference in Lighthouse scores speaks for itself. The WordPress site on shared hosting struggled with performance, while the Astro static site on Cloudflare’s edge network scores near-perfect across the board.

The accessibility score of 98/100 is intentionally not 100 — on some pages I use non-sequential heading levels (e.g. starting from ### instead of ##) to achieve a smaller font size in certain sections.

ServiceProviderCost
WebsiteAstro (hosted on Cloudflare Pages)Free
Domain DNSCloudflareFree
NewsletterSubstackFree
EmailSmall.pl~50 PLN/year
  • Full version control — every change is a Git commit with history. I try to follow the Conventional Commits format for clarity. Visitors can also browse the commit history to see what’s changed
  • Open source — the entire site is on GitHub
  • Nearly free — only ~50 PLN/year for email, everything else is free
  • Speed — static HTML served from edge locations worldwide
  • Content as files — Markdown files I own, not rows in a database
  • Knowledge base — Starlight’s sidebar makes it easy to organise a growing digital garden
  • Work from anywhere — just clone the repo on any device (macOS, Linux, Windows all work fine), run npm run dev, and deploy with a push. No FTP, no hosting panel, no database credentials
  • LLM-friendly codebase — AI coding assistants like Copilot and Claude can read and modify the entire site. In WordPress, LLMs struggled with the PHP/database split and the admin-panel workflow
  • Comments — WordPress had built-in comments. I haven’t considered adding a comments section yet, but feedback is welcome via GitHub issues or in the linked social media posts at the bottom of each blog post
  • Potentially some visitors during migration — there was a brief DNS transition period
  • WYSIWYG editing — but I much prefer writing in Markdown anyway
  1. Export early, clean later — get the content out of WordPress first, then iterate on it in your new setup
  2. Use git fetch --unshallow on Cloudflare Pages — otherwise “Last updated” dates will be wrong
  3. Set up redirects from your old URL structure — don’t break existing links from Google and other sites
  4. Leverage AI for the tedious parts — link fixing, frontmatter generation, grammar review
  5. Don’t aim for perfection on day one — ship it, then improve incrementally

The site is live and I’m happy with the result. The knowledge base is growing, and writing new content is a joy compared to the WordPress admin panel. If you’re running a static-ish blog on WordPress and feeling the friction — I encourage you to make the jump. The tooling in 2026 makes it easier than ever.

The source code is at github.com/pyxelr/pawelcislo.com — feel free to explore for inspiration or open an issue if you have questions.