Skip to content

Render method does not return the finished HTML, rather the unhydrated HTML when used in react + vite app #3181

Closed

Description

@karansethia123

What versions are you using? (if relevant)

@react-email/render -> 2.0.4

Describe the Bug

When the rendered template is copied to clipboard or logged to the console, HTML contains scripts and other elements that result in a broken html being copied to the clipboard. But when copied for the second time, the html returned works which implies a streaming issue happens in the browser.
Is render utility only meant for server side and not browsers, as I am running it in vite react app

What is affected (leave empty if unsure)

Render Utility

Link to the code that reproduces this issue

    const rendered = await pretty(await render(<FinalTemplate content={str} />));
   navigator.clipboard.writeText(rendered);
   toast.message("Email Template copied");

const tailwindConfig = {
  presets: [pixelBasedPreset],
  theme: {
    extend: {
      colors: {
        brand: "#007291",
      },
    },
  },
}



type Props = {
  content: string,
}

export const FinalTemplate = ({ content }: Props) => {


  return <Html>
    <Head />
    <Tailwind config={tailwindConfig}>
      <Body className="bg-white font-google">
        <Container className="my-[30px] mx-auto bg-white rounded-[5px] overflow-hidden">
          <Section className="bg-[#0048C2] p-8 text-center">
            <Img
              src=""
              width="260"
              className="mx-auto block"
            />
          </Section>

          <Section className="px-10">
            <Text className="text-sm leading-[22px] text-[#3c4043]">
              {content}
            </Text>
          </Section>

          <Section
            className="text-center px-8 pt-10 pb-6 bg-[#0048C2]"
          >

            {/* Logo */}
            <Row>
              <Column className="text-center pb-6">
                <Img
                  src=""
                  width="200"
                  style={{ margin: "0 auto", display: "block" }}
                />
              </Column>
            </Row>

            {/* Thin divider */}
            <Row>
              <Column className="px-16 pb-6">
                <div
                  style={{
                    height: 1,
                    backgroundColor: "rgba(255,255,255,0.15)",
                  }}
                />
              </Column>
            </Row>

            {/* Social Icons */}
            <Row>
              <Column className="text-center pb-5">
                {/* Facebook */}
                <Link href="https://facebook.com" style={{ display: "inline-block", margin: "0 10px" }}>
                  <Img
                    src="https://cdn.simpleicons.org/facebook/white"
                    alt="Facebook"
                    width="24"
                    height="24"
                    style={{ display: "inline-block" }}
                  />
                </Link>

                {/* Instagram */}
                <Link href="https://instagram.com" style={{ display: "inline-block", margin: "0 10px" }}>
                  <Img
                    src="https://cdn.simpleicons.org/instagram/white"
                    alt="Instagram"
                    width="24"
                    height="24"
                    style={{ display: "inline-block" }}
                  />
                </Link>
              </Column>
            </Row>

            {/* App Store Badges */}
            <Row>
              <Column className="text-center pb-7">
                {/* App Store */}
                <Link
                  href="https://apps.apple.com"
                  style={{
                    display: "inline-block",
                    marginRight: 6,
                    backgroundColor: "rgba(255,255,255,0.1)",
                    borderRadius: 8,
                    padding: "8px 16px",
                    border: "1px solid rgba(255,255,255,0.25)",
                  }}
                >
                  <Img
                    src="https://cdn.simpleicons.org/appstore/white"
                    alt="App Store"
                    width="20"
                    height="20"
                    style={{ display: "inline-block", verticalAlign: "middle", marginRight: 8 }}
                  />
                  <span
                    style={{
                      fontFamily: "'Barlow', 'Helvetica Neue', sans-serif",
                      color: "#ffffff",
                      fontSize: "12px",
                      fontWeight: 600,
                      verticalAlign: "middle",
                      letterSpacing: "0.04em",
                      textTransform: "uppercase",
                    }}
                  >
                    App Store
                  </span>
                </Link>

                {/* Google Play */}
                <Link
                  href="https://play.google.com"
                  style={{
                    display: "inline-block",
                    marginLeft: 6,
                    backgroundColor: "rgba(255,255,255,0.1)",
                    borderRadius: 8,
                    padding: "8px 16px",
                    border: "1px solid rgba(255,255,255,0.25)",
                  }}
                >
                  <Img
                    src="https://cdn.simpleicons.org/googleplay/white"
                    alt="Google Play"
                    width="20"
                    height="20"
                    style={{ display: "inline-block", verticalAlign: "middle", marginRight: 8 }}
                  />
                  <span
                    style={{
                      fontFamily: "'Barlow', 'Helvetica Neue', sans-serif",
                      color: "#ffffff",
                      fontSize: "12px",
                      fontWeight: 600,
                      verticalAlign: "middle",
                      letterSpacing: "0.04em",
                      textTransform: "uppercase",
                    }}
                  >
                    Google Play
                  </span>
                </Link>
              </Column>
            </Row>

            {/* Thin divider */}
            <Row>
              <Column className="px-16 pb-6">
                <div
                  style={{
                    height: 1,
                    backgroundColor: "rgba(255,255,255,0.15)",
                  }}
                />
              </Column>
            </Row>

            {/* Hours of Operation */}
            <Row>
              <Column className="text-center pb-1">
                <Text
                  className="m-0 text-[14px] font-semibold"
                  style={{
                    fontFamily: "'Barlow', 'Helvetica Neue', sans-serif",
                    color: "#ffffff",
                  }}
                >
                  Hours of Operation
                </Text>
              </Column>
            </Row>
            <Row>
              <Column className="text-center pb-5">
                <Text
                  className="m-0 text-[13px]"
                  style={{
                    fontFamily: "'Barlow', 'Helvetica Neue', sans-serif",
                    color: "#a8bde8",
                  }}
                >
                  Mon – Sun: 8am – 9pm
                </Text>
              </Column>
            </Row>

            {/* Address */}
            <Row>
              <Column className="text-center px-6 pb-2">
                <Text
                  className="m-0 text-[13px] leading-relaxed"
                  style={{
                    fontFamily: "'Barlow', 'Helvetica Neue', sans-serif",
                    color: "#a8bde8",
                  }}
                >
                  10880, Mailbu Point
                  <br />
                  California - 90265 United States
                </Text>
              </Column>
            </Row>

            {/* Phone */}
            <Row>
              <Column className="text-center pb-6">
                <Link
                  href="tel:+1908487374837"
                  className="text-[13px] no-underline"
                  style={{
                    fontFamily: "'Barlow', 'Helvetica Neue', sans-serif",
                    color: "#a8bde8",
                  }}
                >
                  1908487374837
                </Link>
              </Column>
            </Row>

            {/* Thin divider */}
            <Row>
              <Column className="px-16 pb-5">
                <div
                  style={{
                    height: 1,
                    backgroundColor: "rgba(255,255,255,0.1)",
                  }}
                />
              </Column>
            </Row>

            {/* Copyright */}
            <Row>
              <Column className="text-center pb-2">
                <Text
                  className="m-0 text-[11px]"
                  style={{
                    fontFamily: "'Barlow', 'Helvetica Neue', sans-serif",
                    color: "#4a6aab",
                  }}
                >
                  Copyright © 
                </Text>
              </Column>
            </Row>

          </Section>


        </Container>
      </Body>
    </Tailwind>
  </Html>
};

export default FinalTemplate;

To Reproduce

To reproduce the issue

  • install the folowing packages on vite + react + typescript app
    "@react-email/components": "1.0.10",
    "@react-email/render": "^2.0.4",
    "@react-email/tailwind": "^2.0.6",
  • Create an email template using the templates from the docs,
  • Add a prop that that passes down a string content
  • Call render function on a click of a button ( see code above )

Expected Behavior

The string returned after awaiting the render utility should ideally be a proper HTML with no scripts or broken HTML

What's your node version? (if relevant)

No response

Activity

  1. changed the title [-]Render method does not return the finished HTML, rather the un hydrated HTML when used in react + vite app[/-] [+]Render method does not return the finished HTML, rather the unhydrated HTML when used in react + vite app[/+] on Apr 27, 2026
  2. Maliik-B commented on Jun 25, 2026

    @Maliik-B
    Contributor

    This should be resolved as of @react-email/render 2.0.6. The repro here is on 2.0.4.

    The symptom (first call returns HTML with streaming script/reveal markers, second call is clean) is the browser render (https://github.com/resend/react-email/blob/canary/packages/render/src/browser/render.tsx) reading the renderToReadableStream output before the async parts finished. On the first call something in the tree suspends (Tailwind, or the font-google fetch), so React emits the shell plus the reveal scripts that swap in the suspended content; on the second call that work is cached, nothing suspends, and you get clean HTML.

    #3091 (#3091) fixed this in 2.0.6 by awaiting stream.allReady before reading the stream. The browser suite has a regression test (https://github.com/resend/react-email/blob/canary/packages/render/src/browser/render-web.spec.tsx) asserting the output has no $RC and no markers after a suspended boundary resolves.

    Upgrading to >= 2.0.6 (current is 2.0.8) should clear it. Worth closing if that does it on your end.

  3. karansethia123 commented on Jun 26, 2026

    @karansethia123
    Author

    Well I got laid off but I'll take your word for it and try it in other projects thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions