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.
| Decision | What we chose | Why it matters |
|---|---|---|
| Sandboxed plugins | Off | Registry plugins need Workers Paid and a Worker Loader binding; we needed none, so we kept the attack surface and the configuration small |
| Site address | Set per environment, never hard-coded | A hard-coded address makes previews send sign-in and email links that point at production |
| Secrets | Platform secrets and encrypted plugin settings only | Nothing sensitive in configuration files, environment variables shipped to browsers, or the repository |
| Production setup | Only on the real domain | emDash's first-run setup binds sign-in to the address it runs on |
| Production database | Never reset | An empty database reopens the public setup wizard |
| Worker location | Pinned 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.
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.
| Page | Database queries | Server time (median) | Time to first byte (median) |
|---|---|---|---|
| Home | 35–40 | 460–590 ms | 0.64–0.89 s |
| Article | 23 | about 305 ms | about 0.5 s |
| Category | 17 | about 320 ms | about 0.5 s |
| Topics | 15 | about 185 ms | about 0.35 s |
| Authors | 11 | about 183 ms | about 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 happened | The cause | What to do instead |
|---|---|---|
| Scheduled posts did not publish at first | The platform's scheduler took about an hour to begin firing after the first deploy, well past the documented window | Verify the scheduler in the logs after launch, with a test post scheduled a few minutes ahead |
| A page rendered without its styles after a deploy | A redirect that lower-cased URLs also lower-cased the build's file names | Exclude build assets from URL normalisation |
| A sponsored post published without its sponsor's name | The database stores yes/no values as 1 and 0, and a check expected true | Treat both forms as set wherever a flag is read |
| Pages were functionally right but visibly off-design | The 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' subscribers | Email-provider webhooks fire for every domain in a shared account | Filter 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:
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.
Point-in-time recovery for 30 days, plus a weekly encrypted copy stored outside Cloudflare. A full restore was rehearsed.
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.
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.



