Docs/Image Optimization

Image Optimization

How PhantomWP downloads, optimizes, and serves WordPress images with responsive sizes and blur placeholders.

Image Optimization

PhantomWP automatically downloads and optimizes WordPress images for your Astro site. Images are served as responsive WebP with blur-up placeholders for fast, smooth loading.

How It Works

PhantomWP uses a two-stage pipeline to handle WordPress images:

StageWhenPurpose
IDE DownloadOn-demand (you click Download)Instant preview during development
Build-Time SyncAutomatic before every buildFull optimization with responsive sizes

Stage 1: IDE Download

When you click Download in the WordPress Settings media section, PhantomWP:

  1. Fetches images from your WordPress media library
  2. Saves originals to src/media/cms/ (for Astro's Image component)
  3. Converts to WebP in public/media/cms/ (for content images)
  4. Updates the media URL map so local paths are used instead of remote WordPress URLs

This gives you working images in the dev preview immediately. The images are committed to your repository so that CI builds don't need to re-download them.

Stage 2: Build-Time Sync

Before every production build, the sync-media script runs automatically and:

  1. Fetches all published posts and pages from WordPress
  2. Finds every image actually used in content (featured images + images in post/page body)
  3. Downloads any new or changed images (skips unchanged ones)
  4. Generates responsive WebP variants at multiple sizes
  5. Generates blur placeholders for progressive loading
  6. Updates all mapping files

Images that were already downloaded via the IDE are skipped -- the build only processes what's new or changed.

Responsive Images

Content images (set:html content from WordPress) are automatically enhanced with responsive srcset attributes.

What Gets Generated

For each image, the build creates WebP variants at these breakpoints:

WidthFilenameUse Case
320pximage-320w.webpMobile phones
640pximage-640w.webpSmall tablets
960pximage-960w.webpTablets / small desktops
1200pximage-1200w.webpLarge desktops
Full sizeimage.webpHigh-res displays

Only variants smaller than the original image are created. A 500px-wide image won't get a 960w variant.

How It Looks in HTML

WordPress content images are rewritten from:

<img src="https://yoursite.com/wp-content/uploads/photo-1024x768.jpg"
     srcset="...wordpress sizes..."
     sizes="...">

To:

<img src="/media/cms/photo.webp"
     srcset="/media/cms/photo-320w.webp 320w,
             /media/cms/photo-640w.webp 640w,
             /media/cms/photo-960w.webp 960w,
             /media/cms/photo.webp 1200w"
     sizes="(max-width: 640px) 100vw, (max-width: 1024px) 80vw, 100vw"
     width="1200" height="800"
     loading="lazy" decoding="async">

The browser automatically picks the best size for the user's screen.

Featured images use Astro's built-in <Image> component, which handles responsive sizing and format conversion at build time. The FeaturedImage component automatically resolves local images from src/media/cms/.

Blur Placeholders (LQIP)

For a smooth loading experience, featured images show a tiny blurred preview while the full image loads.

How It Works

  1. The build generates a 20px-wide WebP thumbnail for each image (about 200-500 bytes)
  2. This is stored as a base64 data URL in image-placeholders.json
  3. The FeaturedImage component renders it as a blurred background
  4. When the full image loads, it fades in over the placeholder

When Placeholders Show

  • Lazy-loaded images (default) -- Shows blur placeholder, then fades in
  • Priority images (priority={true}) -- No placeholder, loads eagerly for above-the-fold content
<!-- Shows blur placeholder while loading -->
<FeaturedImage src={imageUrl} alt="Post thumbnail" />

<!-- No placeholder, loads immediately -->
<FeaturedImage src={imageUrl} alt="Hero image" priority={true} />

Incremental Sync

The build-time sync is smart about avoiding redundant work:

  • Content hashing -- Each image's SHA-256 hash is tracked. If the content hasn't changed, the download is skipped
  • Modification time checks -- Responsive variants are only regenerated when the source is newer
  • Existing placeholder reuse -- Blur placeholders aren't regenerated if they already exist

This means the first build may take longer (downloading all images), but subsequent builds are fast.

File Structure

After images are downloaded and optimized:

src/ media/ cms/ photo.jpg # Original from WordPress banner.png # Original from WordPress lib/ media-map.json # WordPress URL -> local path mapping responsive-map.json # Image -> responsive variant mapping image-placeholders.json # Base64 blur placeholder data .sync-manifest.json # Incremental sync cache (gitignored) public/ media/ cms/ photo.webp # Full-size WebP photo-320w.webp # 320px responsive variant photo-640w.webp # 640px responsive variant photo-960w.webp # 960px responsive variant banner.webp # Full-size WebP banner-640w.webp # Responsive variant

Running the Sync Manually

The sync runs automatically as part of npm run build. To run it manually:

node scripts/sync-media.mjs

This is useful if you want to generate responsive variants and placeholders without doing a full build.

Troubleshooting

Images not loading in dev preview

Download media from WordPress Settings. The build-time sync only runs during npm run build, not during development.

Blur placeholders not showing

  1. Run node scripts/sync-media.mjs to generate placeholders
  2. Check that src/lib/image-placeholders.json exists and has entries
  3. Placeholders only appear on non-priority images

Build is slow (first time)

The first build downloads all images from WordPress. Subsequent builds are much faster thanks to incremental sync. To speed up the first build, download images via the IDE first -- they'll be committed and the build will skip them.

Images look blurry

If content images appear low quality, check that responsive variants were generated. Run node scripts/sync-media.mjs and look for the "Images with responsive variants" count in the output.

Next Steps