Skip to content

Conversation

iagocavalcante
Copy link

@iagocavalcante iagocavalcante commented Oct 1, 2025

Fixes #1767

This commit resolves the issue where React's renderToStaticMarkup encodes quotes and ampersands in style attributes as HTML entities (" and &), which can break:

  • CSS font-family declarations with quoted font names
  • URLs with query parameters in style properties

The fix adds a post-processing step that decodes these entities back to their original characters within style attributes only.

Changes:

  • Added decodeStyleAttributes function to decode " and & in style attributes
  • Added tests to verify correct decoding of quotes and ampersands
  • All existing tests continue to pass

Summary by cubic

Decodes HTML entities in inline style attributes to prevent broken font-family quotes and URL query params after renderToStaticMarkup. Replaces " and & with real characters only inside style="...".

  • Bug Fixes
    • Added decodeStyleAttributes to post-process style values.
    • Restores quotes and ampersands for valid CSS and URLs.
    • Added tests for font-family and background-image URLs.

Fixes resend#1767

This commit resolves the issue where React's renderToStaticMarkup
encodes quotes and ampersands in style attributes as HTML entities
(" and &), which can break:
- CSS font-family declarations with quoted font names
- URLs with query parameters in style properties

The fix adds a post-processing step that decodes these entities
back to their original characters within style attributes only.

Changes:
- Added decodeStyleAttributes function to decode " and & in style attributes
- Added tests to verify correct decoding of quotes and ampersands
- All existing tests continue to pass
Copy link

changeset-bot bot commented Oct 1, 2025

⚠️ No Changeset found

Latest commit: 34733bd

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

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

Copy link

vercel bot commented Oct 1, 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.

3 issues found across 2 files

Prompt for AI agents (all 3 issues)

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


<file name="packages/render/src/render.ts">

<violation number="1" location="packages/render/src/render.ts:14">
Decoding HTML entities inside style=&quot;…&quot; introduces raw quotes/ampersands, breaking the attribute and corrupting CSS/URLs.</violation>
</file>

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

<violation number="1" location="packages/render/src/render.spec.tsx:30">
Style attributes wrapped in double quotes must keep inner quotes encoded; this assertion enforces decoding them, yielding invalid markup.</violation>

<violation number="2" location="packages/render/src/render.spec.tsx:54">
HTML attributes must encode ampersands as `&amp;amp;`; this test insists on raw `&amp;`, which makes the rendered HTML invalid.</violation>
</file>


Since this is your first cubic review, here's how it works:

  • cubic automatically reviews your code and comments on bugs and improvements
  • Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
  • Ask questions if you need clarification on any suggestion

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

// in attribute values, which can break CSS font-family declarations and URLs
return html.replace(/style="([^"]*)"/g, (match, styleContent) => {
const decoded = styleContent
.replace(/&quot;/g, '"')
Copy link
Contributor

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

Choose a reason for hiding this comment

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

Decoding HTML entities inside style="…" introduces raw quotes/ampersands, breaking the attribute and corrupting CSS/URLs.

Prompt for AI agents
Address the following comment on packages/render/src/render.ts at line 14:

<comment>Decoding HTML entities inside style=&quot;…&quot; introduces raw quotes/ampersands, breaking the attribute and corrupting CSS/URLs.</comment>

<file context>
@@ -5,11 +5,24 @@ interface Options {
+  // in attribute values, which can break CSS font-family declarations and URLs
+  return html.replace(/style=&quot;([^&quot;]*)&quot;/g, (match, styleContent) =&gt; {
+    const decoded = styleContent
+      .replace(/&amp;quot;/g, &#39;&quot;&#39;)
+      .replace(/&amp;amp;/g, &#39;&amp;&#39;);
+    return `style=&quot;${decoded}&quot;`;
</file context>

✅ Addressed in ad14252

expect(html).not.toContain('&amp;param');

// Should contain actual ampersands in URLs
expect(html).toContain('param1=value1&param2=value2');
Copy link
Contributor

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

Choose a reason for hiding this comment

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

HTML attributes must encode ampersands as &amp;; this test insists on raw &, which makes the rendered HTML invalid.

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

<comment>HTML attributes must encode ampersands as `&amp;amp;`; this test insists on raw `&amp;`, which makes the rendered HTML invalid.</comment>

<file context>
@@ -12,4 +12,45 @@ describe(&#39;render&#39;, () =&gt; {
+    expect(html).not.toContain(&#39;&amp;amp;param&#39;);
+
+    // Should contain actual ampersands in URLs
+    expect(html).toContain(&#39;param1=value1&amp;param2=value2&#39;);
+  });
 });
</file context>
Fix with Cubic

const html = render(component);

// Should not contain encoded quotes
expect(html).not.toContain('&quot;');
Copy link
Contributor

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

Choose a reason for hiding this comment

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

Style attributes wrapped in double quotes must keep inner quotes encoded; this assertion enforces decoding them, yielding invalid markup.

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

<comment>Style attributes wrapped in double quotes must keep inner quotes encoded; this assertion enforces decoding them, yielding invalid markup.</comment>

<file context>
@@ -12,4 +12,45 @@ describe(&#39;render&#39;, () =&gt; {
+    const html = render(component);
+
+    // Should not contain encoded quotes
+    expect(html).not.toContain(&#39;&amp;quot;&#39;);
+
+    // Should contain actual quotes
</file context>

✅ Addressed in ad14252

@gabrielmfern
Copy link
Member

After testing a bit more thoroughly I don't think it's an issue to have those entities in styles, even though some users said it is, I could not reproduce at all. The only situation guaranteed to have entities fail is in links, and it only breaks, as far as we know, on Azure's email service with click tracking enabled. Can you change things for that?

@iagocavalcante
Copy link
Author

After testing a bit more thoroughly I don't think it's an issue to have those entities in styles, even though some users said it is, I could not reproduce at all. The only situation guaranteed to have entities fail is in links, and it only breaks, as far as we know, on Azure's email service with click tracking enabled. Can you change things for that?

Yes, I will do this change!

@iagocavalcante
Copy link
Author

Something like that , wdyt ?

return html.replace(/href="([^"]*)"/g, (match, hrefContent) => {
    const decoded = hrefContent.replace(/&amp;/g, '&');
    return `href="${decoded}"`;
  });

@gabrielmfern
Copy link
Member

@iagocavalcante I think it'd be better to use https://www.npmjs.com/package/html-entities/v/2.5.6 and run it inside hrefs

@gabrielmfern
Copy link
Member

Seems like your branch is really outdated when compared to canary, can you update?

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