fix(render): prevent react hydration markers on large emails (#2383)

Co-authored-by: Gabriel Miranda <gabrielmfern@outlook.com>
This commit is contained in:
Emilio Heinzmann
2025-07-30 08:53:53 -03:00
committed by GitHub
parent 9e52a9dca3
commit f5f35f136b
9 changed files with 112 additions and 51 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@react-email/render": patch
---
fix hydration markers on React canary/Next.js latest when rendering large email templates
File diff suppressed because one or more lines are too long
@@ -146,4 +146,36 @@ describe('render on the browser environment', () => {
const element = createElement(undefined);
await expect(render(element)).rejects.toThrowErrorMatchingSnapshot();
});
/**
* Create a large email that would trigger React's streaming optimization
* if progressiveChunkSize wasn't set to Infinity
*
* @see https://github.com/resend/react-email/issues/2353
*/
it('should render large emails without hydration markers', async () => {
const LargeEmailTemplate = () => {
const largeContent = Array(100)
.fill(null)
.map((_, i) => (
<p key={i}>
This is paragraph {i} with some content to make the email larger.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
ad minim veniam, quis nostrud exercitation ullamco laboris.
</p>
));
return (
<div>
<h1>Large Email Test</h1>
{largeContent}
</div>
);
};
const actualOutput = await render(<LargeEmailTemplate />);
expect(actualOutput).toMatchSnapshot();
});
});
+1
View File
@@ -51,6 +51,7 @@ export const render = async (node: React.ReactNode, options?: Options) => {
onError(error: unknown) {
reject(error);
},
progressiveChunkSize: Number.POSITIVE_INFINITY,
})
.then(readStream)
.then(resolve)
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -107,4 +107,36 @@ describe('render on the edge', () => {
`"THIS SHOULD BE RENDERED IN PLAIN TEXT"`,
);
});
/**
* Create a large email that would trigger React's streaming optimization
* if progressiveChunkSize wasn't set to Infinity
*
* @see https://github.com/resend/react-email/issues/2353
*/
it('should render large emails without hydration markers', async () => {
const LargeEmailTemplate = () => {
const largeContent = Array(100)
.fill(null)
.map((_, i) => (
<p key={i}>
This is paragraph {i} with some content to make the email larger.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
ad minim veniam, quis nostrud exercitation ullamco laboris.
</p>
));
return (
<div>
<h1>Large Email Test</h1>
{largeContent}
</div>
);
};
const actualOutput = await render(<LargeEmailTemplate />);
expect(actualOutput).toMatchSnapshot();
});
});
@@ -133,4 +133,36 @@ describe('render on node environments', () => {
`"THIS SHOULD BE RENDERED IN PLAIN TEXT"`,
);
});
/**
* Create a large email that would trigger React's streaming optimization
* if progressiveChunkSize wasn't set to Infinity
*
* @see https://github.com/resend/react-email/issues/2353
*/
it('should render large emails without hydration markers', async () => {
const LargeEmailTemplate = () => {
const largeContent = Array(100)
.fill(null)
.map((_, i) => (
<p key={i}>
This is paragraph {i} with some content to make the email larger.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
ad minim veniam, quis nostrud exercitation ullamco laboris.
</p>
));
return (
<div>
<h1>Large Email Test</h1>
{largeContent}
</div>
);
};
const actualOutput = await render(<LargeEmailTemplate />);
expect(actualOutput).toMatchSnapshot();
});
});
+4 -1
View File
@@ -15,7 +15,9 @@ export const render = async (node: React.ReactNode, options?: Options) => {
let html!: string;
if (Object.hasOwn(reactDOMServer, 'renderToReadableStream')) {
html = await readStream(
await reactDOMServer.renderToReadableStream(suspendedElement),
await reactDOMServer.renderToReadableStream(suspendedElement, {
progressiveChunkSize: Number.POSITIVE_INFINITY,
}),
);
} else {
await new Promise<void>((resolve, reject) => {
@@ -27,6 +29,7 @@ export const render = async (node: React.ReactNode, options?: Options) => {
onError(error) {
reject(error as Error);
},
progressiveChunkSize: Number.POSITIVE_INFINITY,
});
});
}