Skip to content

fix: prevent media upload OOM on Workers for large images#262

Merged
ascorbic merged 4 commits intoemdash-cms:mainfrom
BenjaminPrice:fix/media-upload-oom
Apr 6, 2026
Merged

fix: prevent media upload OOM on Workers for large images#262
ascorbic merged 4 commits intoemdash-cms:mainfrom
BenjaminPrice:fix/media-upload-oom

Conversation

@BenjaminPrice
Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes media upload crashing on Cloudflare Workers (128MB memory limit) when uploading large images. generatePlaceholder() decodes entire images to raw RGBA pixels — a 4000×3000 JPEG becomes ~48MB RGBA, which combined with the original buffer exceeds the Worker isolate limit.

Two-layer fix:

  1. Client-side thumbnail: The admin UI already loads images into <img> for dimension detection. For oversized images (decoded RGBA > 32MB), it now generates a 64px canvas thumbnail and sends it alongside the upload. The server generates blurhash from the thumbnail (~16KB RGBA) instead of decoding the full image.
  2. Server-side safety net: For API/CLI uploads without thumbnails, reads dimensions from image headers via image-size and skips placeholder generation when estimated decoded size exceeds 32MB. Images upload fine, just without LQIP.

Closes #261

Type of change

  • Bug fix
  • Feature (requires approved Discussion)
  • Refactor (no behavior change)
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm --silent lint:json | jq '.diagnostics | length' returns 0
  • pnpm test passes (or targeted tests for my change)
  • pnpm format has been run
  • I have added/updated tests for my changes (if applicable)
  • I have added a changeset (if this PR changes a published package)
  • New features link to an approved Discussion: https://github.com/emdash-cms/emdash/discussions/...

AI-generated code disclosure

  • This PR includes AI-generated code

Screenshots / test output

All 2098 tests pass, including 3 new tests for the safety net:

  • returns null when image dimensions from headers exceed memory budget — minimal JPEG with SOF0 declaring 5000×4000
  • returns null when fallback dimensions exceed memory budget — unrecognizable buffer with oversized fallback dims
  • still generates placeholder for small images with dimensions param — regression check

Files changed

File Change
packages/core/src/media/placeholder.ts Add image-size dimension check + dimensions param; skip decode when RGBA > 32MB
packages/core/src/astro/routes/api/media.ts Accept thumbnail from form data; pass to generatePlaceholder instead of full buffer
packages/core/src/components/InlinePortableTextEditor.tsx Generate 64px canvas thumbnail for oversized images in React upload handler
packages/core/src/visual-editing/toolbar.ts Same thumbnail generation in vanilla JS visual editing upload handler
packages/core/tests/unit/media/placeholder.test.ts 3 new tests for the safety net

…er safety net

Large image uploads (5MB+) crash Cloudflare Workers (128MB limit) because
generatePlaceholder() decodes entire images to raw RGBA pixels. A 4000x3000
JPEG becomes ~48MB RGBA, exceeding the isolate memory budget.

Two-layer fix:
- Client-side: browser generates a 64px canvas thumbnail for oversized images
  and sends it alongside the upload. Server generates blurhash from the
  thumbnail (~16KB RGBA) instead of decoding the full image.
- Server-side: reads dimensions from image headers via image-size and skips
  placeholder generation when estimated decoded size exceeds 32MB. This covers
  API/CLI uploads that don't provide thumbnails.
Copilot AI review requested due to automatic review settings April 5, 2026 05:46
@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 5, 2026

🦋 Changeset detected

Latest commit: 9e6513a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 9 packages
Name Type
emdash Patch
@emdash-cms/cloudflare Patch
@emdash-cms/plugin-ai-moderation Patch
@emdash-cms/plugin-atproto Patch
@emdash-cms/plugin-audit-log Patch
@emdash-cms/plugin-color Patch
@emdash-cms/plugin-embeds Patch
@emdash-cms/plugin-forms Patch
@emdash-cms/plugin-webhook-notifier Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses out-of-memory crashes during large image uploads on Cloudflare Workers by avoiding full-resolution image decoding for blurhash/LQIP generation when the decoded RGBA size would exceed a 32MB budget.

Changes:

  • Add a server-side “safety net” in placeholder generation: read image dimensions from headers and skip placeholder generation for oversized images.
  • Generate and upload a small client-side thumbnail for oversized images, and use it for server-side placeholder generation.
  • Add unit tests covering the new placeholder skip behavior.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/core/src/media/placeholder.ts Adds header-based dimension probing + optional dimensions fallback to skip placeholder generation above a decoded-byte threshold.
packages/core/src/astro/routes/api/media.ts Accepts optional thumbnail multipart field and uses it for placeholder generation; otherwise passes client dims to the safety net.
packages/core/src/components/InlinePortableTextEditor.tsx Generates a 64px PNG thumbnail for oversized images and includes it in upload form data.
packages/core/src/visual-editing/toolbar.ts Mirrors thumbnail generation behavior for the visual editing upload flow.
packages/core/tests/unit/media/placeholder.test.ts Adds tests to ensure oversized-dimension images return null placeholder without decoding.
.changeset/fix-media-upload-oom.md Patch changeset documenting the OOM fix strategy.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 5, 2026

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/@emdash-cms/admin@262

@emdash-cms/auth

npm i https://pkg.pr.new/@emdash-cms/auth@262

@emdash-cms/blocks

npm i https://pkg.pr.new/@emdash-cms/blocks@262

@emdash-cms/cloudflare

npm i https://pkg.pr.new/@emdash-cms/cloudflare@262

emdash

npm i https://pkg.pr.new/emdash@262

create-emdash

npm i https://pkg.pr.new/create-emdash@262

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/gutenberg-to-portable-text@262

@emdash-cms/x402

npm i https://pkg.pr.new/@emdash-cms/x402@262

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/@emdash-cms/plugin-ai-moderation@262

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/@emdash-cms/plugin-atproto@262

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/@emdash-cms/plugin-audit-log@262

@emdash-cms/plugin-color

npm i https://pkg.pr.new/@emdash-cms/plugin-color@262

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/@emdash-cms/plugin-embeds@262

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/@emdash-cms/plugin-forms@262

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/@emdash-cms/plugin-webhook-notifier@262

commit: 9e6513a

Naive sizing (thumbW=64, thumbH=(h/w)*64) could produce an enormous canvas
for very tall or very wide images — e.g. a 100x840000 image would allocate
a 64x537600 canvas client-side, reintroducing the memory blowup this feature
exists to prevent.

Extract computeThumbnailSize() that fits the image within a 64x64 box by
scaling against max(width, height), wrap canvas allocation and drawImage
in try/catch with a no-thumbnail fallback, and add unit tests covering
extreme aspect ratios.
@ascorbic ascorbic enabled auto-merge (squash) April 6, 2026 07:13
@ascorbic ascorbic merged commit 8c693b5 into emdash-cms:main Apr 6, 2026
25 checks passed
@emdashbot emdashbot bot mentioned this pull request Apr 6, 2026
@BenjaminPrice BenjaminPrice deleted the fix/media-upload-oom branch April 6, 2026 07:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Media upload crashes on Cloudflare Workers for large images (OOM in blurhash generation)

3 participants