Skip to content
On this pageHow EmDash Stores and Serves Images
  1. How EmDash Stores and Serves Images
  2. The Image Field Shape
  3. Using the Image Component
  4. Preventing Layout Shift (CLS)
  5. Format Optimization

EmDash Image Optimization: What You Need to Know

Ben 3 min read

When I uploaded my first image through the EmDash admin panel, I expected to deal with resize scripts, CDN configuration, and format conversion pipelines. I didn't have to. EmDash stores images in Cloudflare R2 and the edge cache handles delivery — it just works.

But there are things you need to know to use it correctly. The biggest one: image fields are objects, not strings. If you approach them like WordPress's attachment URLs, you'll spend an hour debugging why your image renders as [object Object]. Here's the full picture.

How EmDash Stores and Serves Images

Images uploaded through the admin panel go into Cloudflare R2 — object storage that's globally distributed and served from Cloudflare's edge network. There's no separate CDN to configure. When a visitor in Tokyo requests an image, it comes from the nearest Cloudflare data center, not your origin.

R2 has no egress fees — requests are billed but data transfer is free. For an image-heavy site, that's a meaningful cost difference compared to S3 or traditional CDN setups.

Images sit in R2 and get served from Cloudflare's edge. There's no separate CDN to wire up — the infrastructure is the same one running your site.

The Image Field Shape

An image field in EmDash is not a URL string. It's an object with metadata that the Image component uses to generate responsive markup. Understanding this shape is the most important thing to get right before you write a single line of template code.

PropertyTypeDescription
idstringMedia ULID — used internally to reference the stored file
srcstring (optional)Public URL of the image — populated by the loader at render time
altstring (optional)Alt text entered in the media library — always set this in the admin panel
widthnumber (optional)Intrinsic width in pixels — used for aspect ratio and CLS prevention
heightnumber (optional)Intrinsic height in pixels — used for aspect ratio and CLS prevention

Using the Image Component

Import Image from emdash/ui and pass the full image object. The component generates a responsive img element with proper width and height attributes, srcset for multiple resolutions, and lazy loading for below-the-fold images.

src/pages/learn/[slug].astro
---
import { getEmDashEntry } from 'emdash';
import { Image } from 'emdash/ui';

const { slug } = Astro.params;
const { entry, cacheHint } = await getEmDashEntry('learn', slug);

Astro.cache.set(cacheHint);
---

<article>
  {entry.data.featured_image && (
    <!-- Above the fold: no lazy load -->
    <Image
      image={entry.data.featured_image}
      class="hero-image"
      loading="eager"
    />
  )}

  <h1>{entry.data.title}</h1>

  {entry.data.body_image && (
    <!-- Below the fold: lazy load -->
    <Image
      image={entry.data.body_image}
      loading="lazy"
    />
  )}
</article>

Never write src={entry.data.featured_image} directly. The image object isn't a string — it's a structured value. Astro will silently coerce it and you'll get a broken img tag with [object Object] as the src attribute.

Preventing Layout Shift (CLS)

Cumulative Layout Shift is a Core Web Vital. Images without explicit width and height cause the browser to allocate zero space for them initially, then jump the layout when they load. EmDash's Image component uses the stored width and height values to set the aspect-ratio CSS property — the browser reserves the right amount of space before the image arrives.

This works automatically as long as you've stored images through the admin panel, which captures dimensions on upload. If you reference external images without dimensions, you lose this protection. Always upload through EmDash rather than referencing external URLs directly.

Format Optimization

Cloudflare's image resizing service can serve WebP or AVIF variants automatically when the Accept header indicates browser support. This is handled at the edge — you upload a JPEG, Cloudflare serves WebP to supporting browsers and JPEG as a fallback. No conversion step in your build pipeline.

The practical result: you don't need to maintain multiple image formats. Upload the highest quality original you have. Cloudflare handles format negotiation, resizing, and caching per variant. Your storage holds one file; browsers receive the optimal format.

Next: Create a custom EmDash theme