EmDash CMS runs on Cloudflare’s serverless edge — V8 isolates, D1 databases, R2 storage, and Workers compute. That architecture is fast by design, but it rewards a specific optimization approach. Traditional PHP caching strategies (opcode caches, page cache plugins) don’t apply. You need to think in terms of request counts, cold starts, and edge latency.
The fastest way to speed up EmDash is to reduce the number of Worker invocations per page view. Each request that hits your origin triggers a Worker, a D1 query, and potentially a KV lookup — all billed separately. Optimize that chain, and your site gets faster and cheaper simultaneously.
The fastest way to speed up EmDash is to reduce the number of Worker invocations per page view.
Here are eight non-obvious performance techniques for EmDash CMS, ordered from highest impact to most situational.
1. Cache Rendered Pages at the Edge with Cloudflare Cache API
EmDash uses Astro for the frontend. Astro can generate static HTML at build time, but dynamic content (drafts, user-specific pages) requires server-side rendering. Every SSR request spins a Worker.
Set a Cache-Controlcodecode header with a long TTL (e.g., public, s-maxage=86400codecode) on pages that change infrequently. Cloudflare’s edge will serve the cached version without hitting the Worker. Use the cf.cacheEverythingcodecode option in your Worker fetch handler. For authenticated pages, use privatecodecode cache and rely on the Worker only for session validation.
This single change cuts origin requests by 80-90% on most content-driven sites. The Worker still runs for cache misses, but that’s a fraction of traffic.
2. Batch D1 Queries Instead of N+1 Patterns
D1 bills per row read and per query. A blog page that fetches a post, then loops through comments with individual queries, will cost ten times more than a single JOINcodecode. Worse, each query adds latency — D1 cold starts add ~50ms.
Use D1’s batch()codecode API to send multiple statements in one round trip. Or restructure your schema to embed related data (e.g., store comment IDs as a JSON array in the post row). For read-heavy sites, consider materializing aggregated data into a separate table that you update on write.
The default EmDash installation uses SQLite locally and D1 on Cloudflare. D1 supports prepared statements and transactions. Use them. A single transaction with multiple reads is cheaper and faster than individual queries.
3. Offload Image Transformations to Cloudflare Image Resizing
EmDash stores images in R2. Serving a full-resolution 4000px photo to a mobile visitor wastes bandwidth and Worker CPU time. Cloudflare Image Resizing (part of the Image Optimization add-on) can resize, format, and cache images at the edge — without a Worker invocation.
Configure your Astro template to request images via /cdn-cgi/image/width=800,format=webp/{path}codecode. The transformation happens on Cloudflare’s edge, not in your Worker. The cached result serves instantly on subsequent requests. No Worker bill, no D1 read for image metadata.
This is especially powerful for portfolio and marketing sites where images dominate page weight. The Image Resizing add-on costs a flat monthly fee, but it eliminates the compute cost of server-side image processing.
4. Use KV for Session Data, Not D1
EmDash uses passkey authentication by default, which avoids password storage. But session tokens still need a lookup. If you store session data in D1, every authenticated page view incurs a database read. That’s wasteful.
Store session tokens in Cloudflare KV with a TTL equal to your session duration. KV reads are fast (~5ms) and cost less than D1 reads. More importantly, KV reads don’t consume D1’s row quota. For high-traffic authenticated sites, this can cut D1 costs by 40-60%.
One caveat: KV is eventually consistent. For session validation, eventual consistency is acceptable — a stale session token just means the user re-authenticates. That’s rare.
5. Set CPU Time Limits on Plugins to Prevent Runaway Workers
EmDash’s sandboxed plugins run in dynamic Workers. By default, a plugin can consume up to 30 seconds of CPU time per invocation. A buggy plugin that enters an infinite loop will max out CPU and inflate your bill.
Set a CPU time limit per plugin in your wrangler.tomlcodecode or via the Cloudflare dashboard. For most plugins, 5 seconds is generous. This doesn’t affect performance under normal conditions — it only caps runaway code. It also protects against malicious plugins that try to mine cryptocurrency or brute-force credentials.
The limit is enforced at the isolate level. The Worker is terminated after the threshold, and the request returns a 503. That’s better than an unbilled CPU spike.
6. Pre-warm Static Routes with Astro’s build Output
EmDash ships with Astro, which supports static site generation (SSG). For content that doesn’t change per request — blog posts, about pages, portfolio items — build them as static HTML during deployment. Astro outputs flat files to Cloudflare Pages (or your own origin). Those files never invoke a Worker.
The default EmDash starter uses SSR for everything. That’s fine for development, but for production, move content that changes infrequently to static routes. Use Astro’s getStaticPaths()codecode to pre-render all posts at build time. The build runs once per deployment. Visitors get HTML served directly from Cloudflare’s edge cache — zero Worker invocations, zero D1 reads.
Combine this with tip #1: static files plus edge caching means your origin almost never sees traffic for those pages.
7. Reduce Worker Invocations with a Service Worker Cache
This is the advanced one. EmDash’s frontend editor (the “edit in place” feature) makes live changes that require a Worker round trip. But for read-only visitors, you can install a service worker that intercepts navigation requests and serves cached HTML from the browser’s Cache API.
The service worker checks the Cloudflare Cache API first, then falls back to the network. For repeat visits, the page renders from the local cache — zero network latency. This technique is common in progressive web apps but rarely applied to CMS frontends.
Implement it in your Astro layout by registering a service worker that caches the current page after first load. On subsequent visits, the service worker returns the cached version instantly. The Worker is only hit on the first request or when the cache is invalidated.
This tip is situational — it works best for content that changes infrequently and where client-side caching is acceptable. For editorial sites with real-time updates, skip it.
8. Monitor Request Counts per Route and Set Budget Alerts
Performance isn’t just speed — it’s cost stability. EmDash on Cloudflare’s paid plan ($5/month) includes 10 million Worker requests. A single DDoS attack or a spike from a viral post can blow through that in hours. Cloudflare offers no built-in spending cap.
Set up budget alerts via Cloudflare’s billing API or third-party tools. Monitor request counts per route using Workers Analytics. If a specific route (like the admin panel or a search endpoint) starts consuming disproportionate requests, investigate. It might be a bot crawling your site or a misconfigured plugin polling too frequently.
For critical sites, configure rate limiting in Cloudflare WAF to block aggressive IPs before they hit your Worker. Rate limiting is free on the paid plan. Without it, a bot attack can degrade performance for real users by consuming all available Worker concurrency.
This tip is about protecting performance from external factors — a point most developers overlook when tuning serverless CMSs.
- What is the fastest way to speed up EmDash?The fastest way is to reduce the number of Worker invocations per page view by caching rendered pages at the edge.
- How can I reduce D1 query costs in EmDash?Batch D1 queries using the batch() API or restructure your schema to embed related data, reducing the number of queries.
- How do I optimize images in EmDash without using a Worker?Use Cloudflare Image Resizing to resize and cache images at the edge via URL parameters, avoiding Worker invocation.