Skip to content

fix(editor): load the email code block renderer lazily - #3843

Merged
dielduarte merged 1 commit into
canaryfrom
perf/editor-lazy-code-block
Oct 30, 2026
Merged

dielduarte merged 1 commit into
canaryfrom
perf/editor-lazy-code-block

Conversation

@vcapretz

@vcapretz vcapretz commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Every bundle that mounts the editor ships all 371 Prism grammars and every react-email component, even when no email is ever rendered from it. On resend.com this is a 1.36 MB (427 KB gzip) chunk on the homepage, which only hosts an editor demo.

The cause is the code block's email renderer in extensions/code-block.tsx:

  • import * as ReactEmailComponents from 'react-email', read by a dynamic key to pick the theme, so bundlers can't tree-shake any react-email export (Tailwind, Markdown, every theme, CodeBlock, etc.).
  • CodeBlock imported eagerly, and CodeBlock imports prism.ts with every grammar.

Changes

  • extensions/code-block-email.tsx (new): the email renderer, with an explicit map of react-email's 38 code block themes in place of the namespace lookup. Unknown names, including the 'default' theme, still fall back to the gray block.
  • extensions/code-block.tsx: renderToReactEmail renders that module through React.lazy. render() already wraps the tree in <Suspense> and awaits allReady (browser and node builds), so the composed HTML is unchanged: the existing Dracula snapshot passes as is. tsdown emits code-block-email-*.mjs/.cjs as a separate chunk.
  • code-block.spec.tsx: a test that every theme react-email exports is in the map, so adding a theme to react-email without adding it here fails CI (checked by removing one).
  • Changeset: @react-email/editor patch.

Impact

Bundling StarterKit from @react-email/editor/extensions for the browser with rolldown, react and react-dom external:

eager JS (gzip) Prism grammars in eager JS lazy JS (gzip)
before 611 KB 371 81 KB
after 201 KB 12 (the in-editor highlighter's) 211 KB

In a Next.js 16 (Turbopack) production build of resend.com with the same change applied as a patch, the 427 KB gzip chunk leaves the homepage and the editor pages, and the lazy chunk is referenced by no route. esbuild's splitter still hoists CodeBlock into a shared chunk because the eager graph reaches react-email's index.mjs, so esbuild users see a smaller win.

Testing

  • pnpm test:unit in packages/editor: 54 files, 522 tests pass (521 before plus the new one).
  • pnpm typecheck, pnpm lint, pnpm build.
  • In resend.com, composed emails with code blocks still come out highlighted in the chosen theme (composeReactEmail in jsdom and in the browser editor).

Not in this PR

prism-plugin.ts still does import(\prismjs/components/prism-${language}`), which makes bundlers emit a chunk for every Prism component; resend.com patches it to a fixed list. A loadLanguageoption onCodeBlockPrism` would let apps choose, but it changes the API, so it's left for a separate discussion.


Summary by cubic

Loads the email code block renderer lazily so bundles that mount the editor no longer ship all 371 Prism grammars and every react-email component. On resend.com this removes a 427 KB gzip chunk from the homepage.

  • Moves the renderer into a new module with an explicit map of react-email's code block themes; React.lazy loads it only when an email with a code block is rendered.
  • Composed HTML is unchanged because render() already waits for the lazy chunk inside its Suspense boundary.
  • Adds a test that fails CI when react-email exports a theme not in the map.

Written for commit c13f8a8. Summary will update on new commits.

View guided diff

The code block's email renderer read themes off `import * as` react-email
by name, which keeps every react-email export alive, and imported
CodeBlock eagerly, which carries all 371 Prism grammars. Both ended up in
every bundle that mounts the editor. The renderer now lives in its own
module, loaded with React.lazy when an email with a code block is
rendered; render() already waits for it inside its Suspense boundary.
@changeset-bot

changeset-bot Bot commented Oct 30, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: c13f8a8

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

This PR includes changesets to release 1 package
Name Type
@react-email/editor 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

@vercel

vercel Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
react-email Ready Ready Preview Oct 30, 2026 3:32pm UTC
react-email-demo Ready Ready Preview Oct 30, 2026 3:32pm UTC

Request Review

@pkg-pr-new

pkg-pr-new Bot commented Oct 30, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@react-email/editor@3843

commit: c13f8a8

@klotty klotty left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No issues found across 4 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Auto-approved: Defers the email code block renderer into a lazy chunk via an explicit react-email theme map, so editor bundles stop eagerly shipping all Prism grammars and react-email components; rendered output is unchanged and a test guards theme-map drift.

View guided diff | Re-trigger cubic

@vcapretz vcapretz changed the title perf(editor): load the email code block renderer lazily fix(editor): load the email code block renderer lazily Oct 30, 2026

@dielduarte dielduarte left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM! did some local tests and no regressions found

@dielduarte
dielduarte changed the base branch from main to canary October 30, 2026 17:52
@dielduarte
dielduarte merged commit c3354ea into canary Oct 30, 2026
17 of 20 checks passed
@dielduarte
dielduarte deleted the perf/editor-lazy-code-block branch October 30, 2026 18:29

This branch was successfully deployed

2 active deployments
Preview – react-email-demo — c13f8a8e Deployed Oct 30, 2026 by vercel[bot]
Preview – react-email — c13f8a8e Deployed Oct 30, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants