Skip to content

Conversation

iagocavalcante
Copy link

@iagocavalcante iagocavalcante commented Oct 8, 2025

Fixes #1767

Summary

This PR resolves the issue where React's rendering encodes quotes in style attributes as HTML entities ("), which can break CSS font-family declarations with quoted font names.

Changes

  • Created decodeAttributeEntities utility function to selectively decode HTML entities
  • style attributes: Decodes ", ', ', and & to fix font-family and other CSS declarations
  • href attributes: Only decodes & to fix URL query parameters while preserving HTML structure
  • Applied the fix to all render implementations (node, browser, edge)
  • Added comprehensive tests to verify correct decoding behavior

Example

Before:

<body style="font-family:&quot;Helvetica Neue&quot;,Arial,sans-serif">

After:

<body style="font-family:"Helvetica Neue",Arial,sans-serif">

Test Plan

  • Added tests for decoding ampersands in href attributes
  • Added tests for decoding quotes in style attributes
  • Added test to verify quotes in href are NOT decoded (to avoid breaking HTML)
  • All existing tests pass

Summary by cubic

Fixes broken CSS font-family and URL query params by decoding safe HTML entities in style and href attributes after render. Applied to node, browser, and edge renderers.

  • Bug Fixes
    • Added decodeAttributeEntities utility to selectively decode attribute values.
    • Style attributes: decode quotes and ampersands to restore quoted font-family and CSS values.
    • Href attributes: decode only ampersands to keep query params intact without breaking HTML.
    • Applied decoding in all render implementations and added tests to cover both cases.

Fixes resend#1767 - This resolves the issue where React's renderToStaticMarkup
encodes quotes in style attributes as HTML entities (&quot;), which can
break CSS font-family declarations with quoted font names.

The fix:
- Created decodeAttributeEntities utility function
- Decodes &quot;, &#x27;, &resend#39;, and &amp; in style attributes
- Only decodes &amp; in href attributes to preserve HTML structure
- Added comprehensive tests to verify correct decoding

Changes:
- packages/render/src/shared/utils/decode-html-entities.ts: New utility
- packages/render/src/node/render.tsx: Apply decoding after rendering
- packages/render/src/browser/render.tsx: Apply decoding after rendering
- packages/render/src/edge/render.tsx: Apply decoding after rendering
- packages/render/src/node/render-node.spec.tsx: Added tests
Copy link

changeset-bot bot commented Oct 8, 2025

🦋 Changeset detected

Latest commit: 9c37138

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

This PR includes changesets to release 3 packages
Name Type
@react-email/render Patch
@react-email/components Patch
playground 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

vercel bot commented Oct 8, 2025

@iagocavalcante is attempting to deploy a commit to the resend Team on Vercel.

A member of the Team first needs to authorize it.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 6 files

Prompt for AI agents (all 1 issues)

Understand the root cause of the following 1 issues and fix them.


<file name="packages/render/src/node/render.tsx">

<violation number="1" location="packages/render/src/node/render.tsx:47">
Decoding the rendered HTML here replaces `&amp;quot;` inside `style` attributes with raw `&quot;`, so markup like `style=&quot;font-family:&amp;quot;Helvetica Neue&amp;quot;,Arial&quot;` becomes `style=&quot;font-family:&quot;Helvetica Neue&quot;,Arial&quot;`; the double quote now closes the attribute early, yielding invalid HTML and dropping the intended styles.</violation>
</file>

React with 👍 or 👎 to teach cubic. Mention @cubic-dev-ai to give feedback, ask questions, or re-run the review.

'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">';

const document = `${doctype}${html.replace(/<!DOCTYPE.*?>/, '')}`;
const decodedHtml = decodeAttributeEntities(html);
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Oct 8, 2025

Choose a reason for hiding this comment

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

Decoding the rendered HTML here replaces &quot; inside style attributes with raw ", so markup like style="font-family:&quot;Helvetica Neue&quot;,Arial" becomes style="font-family:"Helvetica Neue",Arial"; the double quote now closes the attribute early, yielding invalid HTML and dropping the intended styles.

Prompt for AI agents
Address the following comment on packages/render/src/node/render.tsx at line 47:

<comment>Decoding the rendered HTML here replaces `&amp;quot;` inside `style` attributes with raw `&quot;`, so markup like `style=&quot;font-family:&amp;quot;Helvetica Neue&amp;quot;,Arial&quot;` becomes `style=&quot;font-family:&quot;Helvetica Neue&quot;,Arial&quot;`; the double quote now closes the attribute early, yielding invalid HTML and dropping the intended styles.</comment>

<file context>
@@ -43,7 +44,8 @@ export const render = async (node: React.ReactNode, options?: Options) =&gt; {
     &#39;&lt;!DOCTYPE html PUBLIC &quot;-//W3C//DTD XHTML 1.0 Transitional//EN&quot; &quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd&quot;&gt;&#39;;
 
-  const document = `${doctype}${html.replace(/&lt;!DOCTYPE.*?&gt;/, &#39;&#39;)}`;
+  const decodedHtml = decodeAttributeEntities(html);
+  const document = `${doctype}${decodedHtml.replace(/&lt;!DOCTYPE.*?&gt;/, &#39;&#39;)}`;
 
</file context>
Fix with Cubic

Copy link

socket-security bot commented Oct 8, 2025

Copy link

socket-security bot commented Oct 8, 2025

Caution

Review the following alerts detected in dependencies.

According to your organization's Security Policy, you must resolve all "Block" alerts before proceeding. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Block Medium
@swc/core@1.3.101 has Install scripts.

Install script: postinstall

Source: node postinstall.js

From: pnpm-lock.yamlnpm/@swc/core@1.3.101

ℹ Read more on: This package | This alert | What is an install script?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not be running non-essential scripts during install and there are often solutions to problems people solve with install scripts that can be run at publish time instead.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@swc/core@1.3.101. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@gabrielmfern
Copy link
Member

gabrielmfern commented Oct 9, 2025

Can you also run pnpm lint:fix?

'@react-email/render': patch
---

Fix HTML entity encoding in style attributes
Copy link
Member

Choose a reason for hiding this comment

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

can we stick with just this initial portion here, and can you make it all lowercase?

: (processed.type as React.FC);

const rendered = OriginalComponent(processed.props);
// Handle async Server Components
Copy link
Member

Choose a reason for hiding this comment

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

this seems unrelated to the pull request

Copy link
Author

Choose a reason for hiding this comment

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

Right, I tried to fix because of build failing

Copy link
Member

Choose a reason for hiding this comment

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

Let's not do that here.

const decodeHrefValue = (value: string): string => {
// Only decode ampersands in hrefs to fix URL query parameters
// Do NOT decode quotes to avoid breaking the attribute syntax
return value.replace(/&amp;/g, '&');
Copy link
Member

Choose a reason for hiding this comment

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

can you elaborate on why you didn't use html-entities instead of a replace here?

Copy link
Author

Choose a reason for hiding this comment

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

Using html-entities would be overkill for this single character replacement and this only touch in &amp; nothing else, that's make sense?

@gabrielmfern gabrielmfern force-pushed the canary branch 3 times, most recently from 8128be7 to 19de23f Compare October 17, 2025 20:28
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.

Incorrect display of quotes in styles and string links when exporting in react-email

2 participants