emDash Now is a publication about emDash, and it runs on emDash. That makes it a useful test case: a real site, with a newsletter, contact forms, scheduled publishing and a design system to honour, built in about a week and now in production on Cloudflare. This piece sets out what that took, what surprised us, and what it cost in performance terms, with the numbers measured on the live site.

It is written for the people deciding whether to commit to the platform — owners, the agencies that build for them, and the teams who will run the result. The technical detail is here only where it changes a decision.

The architecture, and the switches we left off

The site runs emDash on Cloudflare Workers, with the platform's standard building blocks: a D1 database, R2 for media, KV for an object cache, the Workers Cache for rendered pages, and Cloudflare Images. The front end is Astro, built against a vendored design system. The content model already includes a directory, jobs, courses, events and advertising, but those surfaces are switched off until the audience justifies them — the models exist, and no public route, menu item or sitemap entry reaches them.

Several decisions were made on day one and never reopened. They are worth copying.

DecisionWhat we choseWhy it matters
Sandboxed pluginsOffRegistry plugins need Workers Paid and a Worker Loader binding; we needed none, so we kept the attack surface and the configuration small
Site addressSet per environment, never hard-codedA hard-coded address makes previews send sign-in and email links that point at production
SecretsPlatform secrets and encrypted plugin settings onlyNothing sensitive in configuration files, environment variables shipped to browsers, or the repository
Production setupOnly on the real domainemDash's first-run setup binds sign-in to the address it runs on
Production databaseNever resetAn empty database reopens the public setup wizard
Worker locationPinned next to the database (Targeted Placement)Every page render makes many database round trips; distance multiplies them

On placement, the documentation moved during the build: it now says emDash's query batching ("coalescing") is compatible with pinned placement. We measured it rather than adopting it. Render times overlapped the default configuration within run-to-run noise, and one write was not visible to a read that followed it, so we kept the default. The lesson generalises: when the docs and your measurements disagree, the measurement on your own site wins, and the difference is written down.

Caching: the decision that shapes everything else

Every uncached emDash page is assembled on request from the database. Caching the rendered page at Cloudflare's edge is therefore the decision with the largest effect on speed, hosting cost and how quickly an edit appears — and those three pull in different directions.

We tried both ends. With no edge caching, every edit was visible immediately, but every visit paid for a full render: on emDash 1.2, the median article took 1.29 seconds to start arriving, and one request in twenty took more than four. With a five-minute edge cache and an instant purge when something is published, the picture changes completely.

27 msmedian response for a cached page (19 of 20 requests served from the edge)
5 minmaximum age of a cached page; a publish purges the affected pages immediately
≤ 300 sworst-case window in which a just-published change might not yet show

The trade is explicit. Publishing purges the affected pages, so an edit normally appears at once, but a purge can occasionally complete just after the next visitor has been served, and Cloudflare limits how often purges can run. The worst case is bounded: a page can never be more than five minutes old. For a publication whose content changes a few times a day, that is the right trade; for a live-results page it would not be.

Two traps are worth knowing in advance:

  • Serving stale while refreshing. A common caching setting returns the old page while fetching a new one in the background. Combined with a deployment, it kept serving pages that referenced the previous build's stylesheet for minutes after the new version was live. We turned that setting off.
  • Deployments delete old files. A cached page from before a deploy still points to the previous build's assets, which no longer exist. We now carry the previous build's files forward for seven days, and the deploy fails if any page references a file that does not load.

What an uncached page really costs

A cache miss on production is not just a slower page; it is a full rebuild from the database. emDash reports its own timings on every response, which made the cost measurable. These are cache-filling renders on emDash 1.3, sampled on 18 October 2026, fifteen requests per page.

PageDatabase queriesServer time (median)Time to first byte (median)
Home35–40460–590 ms0.64–0.89 s
Article23about 305 msabout 0.5 s
Category17about 320 msabout 0.5 s
Topics15about 185 msabout 0.35 s
Authors11about 183 msabout 0.34 s

Two further findings matter for planning. First, a server that has not handled a request recently starts cold, which added roughly 0.3 to 0.5 seconds of database initialisation; because the Worker is pinned to a region with many machines, a quiet site meets cold starts more often than a busy one. Second, the query count is a design decision. On emDash 1.2 we cut the home page from 82 database queries to 54 and the article from 51 to 36, mostly by not loading data the page never displayed. Part of today's lower figures reflects a leaner content set as well as the upgrade, so treat them as current, not as a before-and-after.

The practical conclusion: on a cached site, visitors rarely pay these costs. They matter for the first visitor after each cache expiry, for search crawlers, and for pages too personal or too fresh to cache.

Previews, access and the parity problem

Every change to the site is deployed first as a private preview, protected behind Cloudflare Access so that only the team and the automated tests can reach it. That arrangement is sound, and it surfaced three practical lessons.

  • Previews are not production. Preview deployments have no edge cache and no purge, so anything that depends on caching can only be proven on the live site.
  • Sign-in is tied to the address. Passkeys belong to the hostname they were created on, and each preview has its own, so automated tests authenticate with a scoped access token instead.
  • Parity has to be checked, not assumed. The one failure at launch was a newsletter form that worked on every preview and not in production, because a setting existed only in the preview environment. Every value that differs between environments now has a check that runs against production after each deploy.

Things that took longer than they should

Most of the build went to plan. A handful of issues cost disproportionate time, and each has a simple preventive step.

What happenedThe causeWhat to do instead
Scheduled posts did not publish at firstThe platform's scheduler took about an hour to begin firing after the first deploy, well past the documented windowVerify the scheduler in the logs after launch, with a test post scheduled a few minutes ahead
A page rendered without its styles after a deployA redirect that lower-cased URLs also lower-cased the build's file namesExclude build assets from URL normalisation
A sponsored post published without its sponsor's nameThe database stores yes/no values as 1 and 0, and a check expected trueTreat both forms as set wherever a flag is read
Pages were functionally right but visibly off-designThe acceptance criterion said 'uses the design system', not 'matches the design'Accept pages against side-by-side UI-kit design
A webhook could have acted on other sites' subscribersEmail-provider webhooks fire for every domain in a shared accountFilter every event by the sending domain before acting on it

Owning the newsletter

The subscriber list lives in emDash, not in the email provider. The provider is a transport: it sends what the site tells it to, and its audience is a synchronised copy. That is a deliberate commercial choice — the list is the publication's most valuable asset, and keeping it on the platform means the provider can be replaced without migrating it. The same thinking put the welcome and confirmation letters inside the site, and made the confirmation link require a click on a page rather than a simple visit, so that email security scanners which pre-open links cannot subscribe anyone by accident.

Security, privacy and SEO

emDash ships the essentials: secure default headers, encrypted plugin secrets, and SEO metadata, sitemaps and structured data. We added a content security policy, rolled out in report-only mode first so that nothing broke; strict transport security; and cookieless analytics, which keeps the site free of consent banners. On search, the additions were modest — breadcrumb, organisation and review data, a news sitemap, and canonical redirects for trailing slashes and capital letters — and a launch crawl found no broken links once the trust pages were published.

Operations: rehearsed before they were needed

Three safeguards were set up and tested before launch rather than after an incident:

01Migrations

Database changes run in the deploy pipeline after a recovery point is recorded; the live site refuses to serve rather than change its schema mid-request.

02Backups

Point-in-time recovery for 30 days, plus a weekly encrypted copy stored outside Cloudflare. A full restore was rehearsed.

03Key rotation

The key that protects stored secrets was rotated end to end on a preview, so the production procedure is written and proven.

One finding from that work changes how owners should think about backups: emDash's built-in export is not a restore. A site comes back from the database's own recovery point or from a full database dump, together with the media and the encryption key — so those three, not the export file, are what a backup plan has to protect.

Before you launch an emDash site0/7 done

The recommendation, by where you sit

  • Running a WordPress site that works. Nothing here argues for an urgent move. It does show that the operational questions — caching, backups, recovery — have good answers on emDash, so they need not block a later decision. The business case is the gating question, and our production-readiness verdict sets out when to look again.
  • Commissioning a new site. Budget for the decisions above, not just the build. A competent team will make them in the first week; insist on seeing the cache policy, the recovery plan and a side-by-side design acceptance before launch. Running costs are set out in what an emDash site costs to run.
  • Running an agency. The repeatable value is in the operating layer: a cache policy, a parity check, a recovery rehearsal and a design-acceptance step you can apply to every client site. That is where emDash expertise becomes a service, rather than a one-off build.