← All case studies Ghost → Astro

Joshua Warren left Ghost for Astro in about a day

He exported Ghost, converted posts to Markdown, rebuilt the site in Astro and Tailwind, and shipped it to Cloudflare Pages. Redirects took more time than the export.

Joshua Warren moved joshuawarren.com from Ghost to Astro. The new site sits on Cloudflare Pages. On 16 February 2026 he wrote up the switch. This is his own post, not a vendor page.

He said the move took about a day. The export and the Astro build were quick. Redirects ate the rest of the time.

If you are looking at the same route, start with the Ghost → Astro playbook. Joshua’s post is the field report. The playbook is the export, URL registry, and verification path.

Why he left

Ghost had worked. He liked the editor, the publishing flow, and the themes. He had written hundreds of posts on it.

Ghost is a blogging platform. He wanted a personal site that could hold landing pages, the blog, and a design system that stayed consistent. Ghost started to feel like the wrong box for that.

Landing pages were painful. Custom pages in Ghost were not enough. He used code injection as a workaround. Ghost injects header and footer snippets on every page, or on one post from the editor sidebar. That is a hook for CSS and scripts. It is not a page builder.

He also wanted one design system across the blog, landing pages, and static pages. Ghost pages always live at /:slug/. A custom look means a page-:slug.hbs theme file, or more injection. He did not want to keep fighting the theme.

Hosting was the other reason. Ghost Pro felt like overkill for a personal site. He later put the bill at $25 a month. That is his number. Ghost’s pricing page today lists Starter at $18 a month and Publisher at $29 a month when you pay yearly. I am quoting his February 2026 figure, not today’s list.

How he moved it

He lists four steps:

  1. Export all Ghost content
  2. Convert posts to Markdown files with frontmatter
  3. Build the Astro site with Tailwind
  4. Deploy to Cloudflare Pages

Ghost’s export lives under Settings → Advanced → Import/Export. Content and settings come out as JSON. You can also take members, post analytics, themes, and routes.yaml plus redirects.yaml. On Ghost(Pro), media is an extra checkbox. On a self-hosted box, Ghost still wants you to copy the content folder. He says he exported everything. He does not name which boxes he ticked.

He does not name the converter. I am not inventing one. The JSON holds posts, pages, tags, and settings. Markdown with frontmatter is what Astro content collections want.

Astro can keep Ghost as a headless CMS through the Content API. He skipped that. He wanted Markdown in the repo.

The hard part was redirects. He says Ghost URLs do not map perfectly to static file URLs. I cannot see his old routes.yaml. Ghost’s default post permalink is /{slug}/. Pages stay on /:slug/ and you cannot change that. His write-up now lives at /blog/migrated-site-ghost-astro/. If posts move under /blog/, every old post needs a hop.

Astro can hold those hops in config redirects. On Cloudflare Pages you can also drop a _redirects file in public/. He does not say which file he used. He does say he spent time getting it right, and that he should have planned it first.

Cloudflare has a Pages guide for Astro. Connect the Git repo. Build command npm run build. Output dist. Astro’s own Cloudflare deploy page now steers new sites at Workers. Pages still works. He shipped to Pages.

What he gained

Landing pages and the blog now share one design. He wanted that more than a theme dropdown.

The pages are static HTML on the edge. He says the site loads fast now, without a CMS caveat. He did not publish a Lighthouse score, so I am not inventing one.

The Ghost Pro bill went away. Cloudflare Pages is on the Free plan for the limits he needed. 500 builds a month. 20,000 files. He says that covered his usage.

Posts live as Markdown in Git. He can review a change, run the site locally, and deploy when it looks right. That replaced the Ghost admin for him.

What broke or got skipped

Redirects were the cost he wrote about. He does not describe a 404 after launch, a missing image, or a rollback. The gap he names is the URL map.

He also says he should have started with the Tailwind config and the component library. He rebuilt the design after the content was already in. That cost extra time.

He should have used content collections from the first commit. He converted posts to Markdown with frontmatter. Collections are the typed folder around those files. Doing them later means moving files and rewriting queries.

He left the Ghost admin. If a team publishes through that UI, he says stay. The playbook lists members, newsletters, paid subscriptions, and comments as things you rebuild or drop. He does not mention any of those. I am not filling them in.

Ghost themes do not run on Astro. The playbook already calls the theme a loss. He wanted a new design, so that was the point of the rebuild.

What I would copy

Export from Ghost Admin before you write a single Astro route. Take content and settings. Take redirects and routes. Take media if Ghost Pro offers it, or copy content/images from the server. The JSON file is not a full restore.

Write the URL registry next, not last. List every Ghost post, page, tag, and author path. Decide the Astro path for each one. Keep the public URL or add one 301. Then test the list. Cloudflare Pages caps _redirects at 2,000 static rules and 100 dynamic ones. Hundreds of posts fit. Tens of thousands need Bulk Redirects.

Skip the headless Ghost setup if the point of the move is leaving the platform bill and the admin.

Define the Tailwind tokens and the shared components before you convert the archive. He said that after the fact.

Run the Ghost → Astro playbook. Use Joshua’s post for the one-day personal-site path. The reverse route is Astro → Ghost if you need a way back.