How to use renderToStaticMarkup in NextJS server action

710 views Asked by At

I'm attempting to use renderToStaticMarkup in a NextJS 13 server action to send the generated HTML via email.

import { renderToStaticMarkup } from "react-dom/server";
import { sendEmail } from "@/infra/email";
import { ResetPasswordEmail } from "@/infra/email/templates";
import { userRoutes } from "@/routes";

interface SendPasswordResetLink {
  email: string;
  firstName: string;
  passwordResetToken: string;
}

export const sendPasswordResetLink = async ({ email, firstName, passwordResetToken }: SendPasswordResetLink): Promise<void> => {
  const url = userRoutes.resetPassword.generateUrl(passwordResetToken);
  const subject = `Password reset`;
  const html = renderToStaticMarkup(<ResetPasswordEmail firstName={firstName} url={url} />);
  const result = await sendEmail({ html, subject, toEmail: email, toName: firstName });

  //... more code
};

However I'm getting the following error in the backend: Error: × Expected '>', got 'firstName'

And in VSCode I get red squigly lines and when I hover it says: 'ResetPasswordEmail' refers to a value, but is being used as a type here. Did you mean 'typeof ResetPasswordEmail'?ts(2749)

enter image description here

The ResetPasswordEmail component doesn't have any interactivity, and its filename ends in .tsx.

Any idea how I can fix this, or some other way to send HTML emails in server actions? Thanks!

2

There are 2 answers

0
libra On

the error may be caused by nodejs not recognizing JSX syntax. try use React.createElement instead.

const element = React.createElement(ResetPasswordEmail, {firstName, url})
const html = renderToStaticMarkup(element)

suggestion:

Since you're using NextJS for SSR, maybe it's better to let NextJS handle the rendering, then you can make request to NextJS server to get the rendered HTML and send it by email.

0
livealvi On

The error you're seeing is likely due to a TypeScript type error.

When you use the <ResetPasswordEmail> component in the renderToStaticMarkup function, TypeScript is treating it as a value instead of a type.

to fix this error, you can try importing the ResetPasswordEmail component as a type using the typeof keyword.

Here's an example:

import { renderToStaticMarkup } from "react-dom/server";
import { sendEmail } from "@/infra/email";
import { ResetPasswordEmail } from "@/infra/email/templates";
import { userRoutes } from "@/routes";

interface SendPasswordResetLink {
  email: string;
  firstName: string;
  passwordResetToken: string;
}

export const sendPasswordResetLink = async ({ email, firstName, passwordResetToken }: SendPasswordResetLink): Promise<void> => {
  const url = userRoutes.resetPassword.generateUrl(passwordResetToken);
  const subject = `Password reset`;
  const html = renderToStaticMarkup(<typeof ResetPasswordEmail>{ firstName, url });
  const result = await sendEmail({ html, subject, toEmail: email, toName: firstName });

  //... more code
};

this example, used the typeof keyword to import the ResetPasswordEmail component as a type. also passed the firstName and url props directly to the component using object shorthand notation.

and you can also try using the renderToString function instead of renderToStaticMarkup. renderToString is a server-side rendering function that is designed to work with Next.js.

here's is:

import { renderToString } from "react-dom/server";
import { sendEmail } from "@/infra/email";
import { ResetPasswordEmail } from "@/infra/email/templates";
import { userRoutes } from "@/routes";

interface SendPasswordResetLink {
  email: string;
  firstName: string;
  passwordResetToken: string;
}

export const sendPasswordResetLink = async ({ email, firstName, passwordResetToken }: SendPasswordResetLink): Promise<void> => {
  const url = userRoutes.resetPassword.generateUrl(passwordResetToken);
  const subject = `Password reset`;
  const html = renderToString(<ResetPasswordEmail firstName={firstName} url={url} />);
  const result = await sendEmail({ html, subject, toEmail: email, toName: firstName });

  //... more code
};

in this example, used the renderToString function to render the ResetPasswordEmail component to a string. also passed the firstName and url props directly to the component using object shorthand notation.