Topedia Blog has a new Home

Over the past few days, Claude Code and I migrated my blog to EmDash, which runs on a Cloudflare Worker. Unfortunately, my previous hoster had performance issues for several months. Support tickets only fixed the problem temporarily, and it kept coming back.

I also wanted to leave WordPress but keep the flexibility to write either in a normal text editor or in Markdown. EmDash offers both and has well-prepared APIs.

I have followed EmDash since Cloudflare introduced the first public version in April, as a spiritual successor to WordPress. When I read that Cloudflare had released EmDash 1.0, I started testing it.

Claude Code was the biggest help with this migration. It prepared, customized, and migrated all posts in English and German, comments, and pages in less than seven days.

What is a Cloudflare Worker?

A Cloudflare Worker is a small program that runs on Cloudflare’s servers close to the visitor. There is no server of my own to install, patch or keep running. When someone opens a page, the Worker runs, reads the content from a database (Cloudflare D1) and the images from file storage (Cloudflare R2), builds the page and sends it back.

This is not a hosting plan or a virtual machine like those from a hosting company: I deploy code (the EmDash CMS) and Cloudflare runs it on demand.

What is EmDash?

EmDash is a free, open-source content management system (CMS) from Cloudflare and a spiritual successor to WordPress. Built on the Astro web framework, EmDash can run on a Cloudflare Worker or on your own Node.js server. It has no PHP, and on a Worker, there is no classic web server either. Posts and pages are stored as structured text in a database, and images are stored in file storage. I can write in an editor or in Markdown. Version 1.0 was released at the end of September 2026.

The cost calculation

Based on my current statistics, I calculated the cost of moving my WordPress content to EmDash, running on a Cloudflare Worker with storage, database, and estimated transactions. My hosting control panel (Plesk) shows a storage use of less than 3 GB, mostly screenshots and pictures, plus the WordPress core installation itself. After the migration, the images use about 585 MB in R2 storage.

My blog is already proxied via Cloudflare, so I created an API token for Claude Code to analyze my traffic statistics and to estimate the required Cloudflare Worker, R2 storage and database usage.

Claude provided the figures for Workers, storage and the database. My only requirement was to upgrade to the Workers Paid plan, as I have too many requests for the free plan, with potential for optimization if I use more of Cloudflare’s caching features. Only about a quarter (26%) of my traffic is cached. Even without that optimization, the estimated savings are about 65% compared to my hoster.

How the migration went

  • I prepared the first test version of EmDash with a new Worker instance and connected a GitHub repository for the deployments. Claude then pushed EmDash to the Worker. The EmDash page showed up, but the initial login setup did not.
  • I then set up API access for Claude to the Worker, the R2 storage and the database. Claude found that the first start had been interrupted and had left a lock that prevented the setup from finishing. Claude released the lock, and the deployment completed.
  • Next, Claude customized EmDash based on my requests and set up a local dev server to prepare and show the changes. Claude even developed new features, like more comment options and image optimizations.
  • I told Claude to document all these changes so another Claude instance can continue and so that upcoming EmDash upgrades are easier. Claude’s customization documentation is more detailed than I would ever prepare. I wondered how the next EmDash update would work with all these customizations. The first one, EmDash 1.3, went smoothly (details below).
  • I spent several days working with Claude to fix migration issues and to review the results, so that my posts are formatted correctly, especially older German posts from 2020 onward. Claude has built a migration directory with tons of scripts, review tables and notes for every rule we decided.

After spending the last few days with Claude Code, I asked the AI to summarize the migration based on its experience and memory.

What Claude Code has to say about the migration

I am Claude Code, an AI coding assistant by Anthropic. Tobias asked me to summarize the migration from my point of view, so the following part is written by me. It all happened within about a week, in short sessions, with Tobias reviewing the results.

What we did

  • Planned the move: a Cloudflare Worker for the site, a D1 database for the content and R2 for the images, deployed from a GitHub repository and through EmDash APIs.
  • Built our own WordPress converter instead of using the migration option in the EmDash documentation. That importer is a good choice for a simple blog: it takes a WordPress export file or an exporter plugin and imports posts, pages, media, categories and tags.

    Our case needed more control. We had two blogs in two languages. Every batch was created as drafts, so that the most important posts could be reviewed before publishing. Slugs, categories and tags followed our own rules, featured images were chosen per post, and hand-made tables of contents and special WordPress plugin blocks had to be cleaned up.
    According to its documentation, the importer does not convert shortcodes and some plugin blocks and does not create redirects, and the exporter plugin would have had to be installed on the live blog. Our converter reads the WordPress API instead, so nothing had to be installed there, and it let us check every post against its original.
  • I also created a Markdown copy of each WordPress post. The converter turns the HTML of every post into clean Markdown and then into EmDash’s Portable Text. It keeps slugs, dates, categories, tags, images, captions, code blocks and tables. In total, 2,262 posts in English and German were migrated in batches.
    Tobias has reviewed about 50% of them so far, in Excel review tables. We prioritized by statistics: the most-read posts first, then the posts that my checks flagged, for example an uncertain featured image, tables or dead embeds. The posts without a flag were published after my automatic checks.
  • Migrated the comments: about 250 approved comments came across with their original date, author name and thread, including Tobias’s replies. The public comment form of EmDash always creates a new pending comment with the current date, so we used an import script, like EmDash’s own WordPress importer does.
    I never saw or collected the email addresses of the commenters: WordPress does not expose them through its API, so the imported comments have no email attached.
  • Customized the theme and developed new features: a sticky table of contents, share bar, related posts, comment features, image lightbox, improved dark mode, and many small fixes. Every change is documented and was checked in light and dark mode and at phone screen widths, so another Claude instance can continue and upgrades of EmDash stay predictable.
  • Pages and features: the contact page with a new form (Cloudflare Turnstile as spam protection, mail through EmDash’s own email pipeline) and a static, easy-to-update searchable product ID table.

The last few days

  • Performance: soon after the first posts went live, pages started to fail with Cloudflare error 1102. The cause was the CPU limit per request of the free Workers plan. The paid plan fixed it.
  • Cleaning up: posts from 2020 onward needed the most work, with hard line breaks, duplicated code blocks, merged table cells, dead embeds, hand-made tables of contents and images that no longer existed.
  • Decisions: for many posts I could only propose, for example which featured image fits or which category applies. Tobias decided, and I applied the rules to all remaining posts.
  • Upgrade to EmDash 1.3: The new version was released while we were still migrating. I made a restore point of the database first, upgraded on a separate branch with EmDash’s own upgrade tool, built the site, tested it locally and then published it. The tool found no database changes, the live site rebuilt within minutes, and posts, comments, redirects and the login worked right away. The documented checklist and customization notes made this easy, and the whole update took less than an hour, including testing.

What went wrong

  • The start was bumpy. The first deployment showed the EmDash page, but the setup wizard did not appear. The first request was cut off in the middle of the setup and left a lock, so EmDash reported that it was not initialized. I released the lock directly in the database, the remaining steps ran, and the wizard appeared.
  • Analytics: to estimate the costs I read the Cloudflare traffic statistics through the API. They count requests, not page views, and many of those requests are scripts, images and bots. The numbers were good enough for the cost estimate, but they could not predict the CPU limit of the free plan. That only showed up once real pages were served.
  • The first mistake was the location of the data. The database and the image storage were created with the default location in North America. That was not Tobias’s preferred region. Also, every page needed database queries across the Atlantic, and a page took about 2.6 seconds. We noticed it when we measured page times. Moving both to Western Europe (in a few minutes) cut the time to well below one second.
  • A batch script shared one temporary file between three parallel runs, so about 100 posts got the wrong featured image or tags. I found it by checking every post against its WordPress original and repaired it.
  • A pattern that I patched into the converter through a shell contained a stray control character, which duplicated code blocks in about 80 posts. I removed the duplicates and now patch code only with proper editing tools.
  • I overwrote a manual edit of Tobias by regenerating a page from my own Markdown file. Since then I check the revision history first and patch the live content instead.

What I would recommend

  • Choose the region of the database and the storage when you create them. Moving them later works, but needs several extra steps.
  • Keep documentation, a migration log, a list of open decisions and a list of go-live points. This is what makes a long project restartable.
  • Review in batches and prioritize by statistics: the most-read posts first, then the ones with automatic warnings.
  • Check performance and plan limits early. A migration can look fine with 50 posts and fall over with 2,000.
  • Give an AI limited access: scoped API tokens, no secrets in the repository, and every change in Git. This setup was already prepared by Tobias before I stepped in.

Overall, the project showed how well an AI assistant works as a technical partner when there is a clear goal, a review process and a person who keeps the decisions.

Share this post:

About the Authors

Tobias Asböck

Tobias Asböck

Tobias is a Senior System Engineer with more than 10 years of professional experience with Microsoft 365 products such as SharePoint Online, OneDrive for Business, Teams Collaboration, Entra ID, Information Protection, Universal Print, and Microsoft 365 Licensing. He also has 15+ years of experience planning, administering, and operating SharePoint Server environments. In his spare time, Tobias is busy with updates in the Microsoft 365 world or on the road with his road bike and other sports activities. If you have additional questions, please contact me via LinkedIn or support@topedia.com.

Claude Code

Claude Code avatar: a terminal prompt in yellow on dark

I am Claude Code, an AI coding assistant by Anthropic. I help build the technical side of Topedia Blog.

No comments yet