Back to Blog

Why I Moved My Website to EmDash (and Ditched Ghost + Notion)

For the last while, my website has been held together by two different services. The blog lived in Ghost. The "data" pages — my gear list, my Now page, my projects, what's on my iPhone — all lived in Notion, pulled in through their API. It worked. But it always bugged me that the thing I think of as my site was actually two accounts I was renting, stitched together with API keys.

I wanted it all in one place. I wanted to own the data. And I'd been eyeing a new CMS called EmDash for a while. So I finally did the thing.

This post gets into the weeds on what EmDash is, how it differs from the old WordPress way of doing things, how it actually works, and the migration itself — including the parts that gave me trouble. If you just want the short version: it's great, I'd do it again, and the challenges were mostly self-inflicted. If you want to know how the sausage is made, keep reading.

What Is EmDash?

EmDash is a database-backed CMS built on Astro. That sentence is doing a lot of work, so let me unpack it.

Most CMSes you know are their own world. WordPress is a PHP app. Ghost is a Node app. You run them, and your site is a theme living inside their box. EmDash flips that around: it installs into your own Astro project as an integration. You add a few lines to your config, and suddenly your codebase has a full CMS bolted on — an admin panel, a REST API, authentication, a media library — all running inside the site you control.

Your content lives in a real SQL database that you own (I'm using Turso). Your images live in your own object storage (I'm using Cloudflare R2). There's no "EmDash cloud" holding your stuff hostage. It's open source, you self-host it, and the whole thing is TypeScript and Astro top to bottom.

The other neat trick: content is stored as Portable Text — a structured JSON format instead of a blob of HTML. That sounds nerdy, and it is, but the payoff is that your content isn't welded to one presentation. It's data, and you render it however you want.

EmDash vs WordPress

I've got history with WordPress. I've moved to it, moved off it, and watched the whole Mullenweg vs. WP Engine saga play out from the sidelines. So this comparison isn't coming from someone who's never touched it.

WordPress is incredible at what it does. Massive ecosystem, a plugin for everything, runs a huge chunk of the web. But that power comes with weight: PHP, a separate admin app, a database schema you don't really touch, and a plugin sprawl that slowly turns "my website" into "a stack of 30 plugins I'm afraid to update." And philosophically, the recent drama was a good reminder that you're often building on someone else's platform politics.

EmDash is the opposite trade-off. The ecosystem is tiny and new — there's no plugin-for-everything yet. But:

  • It lives in my codebase, version-controlled in git, and deployed like the rest of my site.
  • I own the data — it's in my database, not a hosted product.
  • The stack is modern and fast (Astro ships almost no JavaScript by default).
  • There's no admin app running separately; it's just routes in my own app.

Funny enough, EmDash's content importer is built around WordPress — it ingests WordPress export files. So even if you're leaving WordPress, it meets you where you are. (More on why that mattered for me in a second.)

The honest summary: WordPress is the safe, batteries-included giant. EmDash is the lean, you-own-everything newcomer. I wanted the second one.

How It Works

Here's the actual stack now that the dust has settled:

  • Astro — the site itself, same as before.
  • EmDash — added as an Astro integration.
  • Turso (libSQL) — the database where all my content lives. It's SQLite, but remote, which matters on serverless.
  • Cloudflare R2 — object storage for images (feature images, gear photos, etc.), served through EmDash's media library.
  • Upstash Redis — stores admin login sessions (more on that headache below).
  • Vercel — where it's all hosted, at least for now.

The magic glue is Astro Live Collections. Instead of rebuilding my whole site every time I publish, my pages query the database at runtime. I add a post or edit my gear list in the admin, and the change shows up on the live site within seconds — no deploy, no rebuild. That was the dream: edit content like a CMS, but with everything still living in my own code and my own database.

The Migration — and the Challenges

This is the part you actually came for. Migrating wasn't a button. It was a project. Here's how it went, bumps included.

The blog had no easy door in. EmDash imports from WordPress, but my blog was in Ghost. So the move was a two-step shuffle: I pulled every post from Ghost via its API and converted them into a WordPress export file, then fed it into EmDash's importer. Roundabout, but it worked — and EmDash handled the HTML-to-Portable-Text conversion for me.

Notion was hand-built. There's no Notion importer, so I scripted the whole thing — pulling each database (gear, projects, now, iPhone) from Notion and pushing the records into EmDash via its API. The fun discovery: a bunch of my images were uploaded into Notion, and Notion's image URLs expire. If I'd just copied the links, half my photos would've died within the hour. So the script downloads each image and reloads it into R2, where it now lives. Permanent, mine.

Then there was the login loop. This one cost me real time. I'd log into the admin with a passkey, and it would immediately bounce me back to the login screen. Turns out two things were conspiring: EmDash's session cookies are marked "secure," which browsers quietly drop over plain http://localhost, and Vercel's serverless setup doesn't give Astro a place to store sessions by default. The fix was a local session store for development and Upstash Redis in production. Once that clicked, the login stuck.

Passkeys are domain-locked. I set up my passkey on localhost, which means it's useless on the real domain — passkeys are tied to the exact site you create them on. So for logging into the live admin, I wired up "Sign in with GitHub." Now I can access my CMS from anywhere, including on my phone.

And a fun head-fake: at one point, every imported post showed today's date, and I briefly panicked that the migration nuked my dates. It hadn't — the admin was just showing the "last modified" column (which was today, because I'd just imported them). The real publish dates were fine. Always read the column header before you panic.

Would I Recommend It?

If you want a turnkey CMS that your non-technical friend can run, this isn't it — yet. EmDash is new, and a migration like mine assumes you're comfortable in the weeds.

But if you're a developer who's tired of your site being three rented services in a trench coat, and you want to genuinely own your content on a modern stack, it's fantastic. My whole site now lives in a single codebase, with a single database I control, and I edit it from a clean admin panel I host myself.

Ghost and Notion served me well. But this finally feels like mine.

🍕 Buy me a pizza