If you're building features like authentication or transactional emails in your Next.js app, chances are you’ll need to include images in your emails. But wait—before you assume you can add images the same way you do for regular web pages, there's a small twist when it comes to email clients.
You might think this works:
import Image from 'next/image';
export default function Page() {
return (
<Image
src="/email-image.png" // From your public folder
// Other props...
/>
);
}
Or maybe this:
import Image from 'next/image';
import img from '../email-image.png';
export default function Page() {
return (
<Image
src={img}
// Other props...
/>
);
}
Both approaches will result in your image being loaded at [host]/email-image.png
or /_next/static/[hash].png
in a browser. The browser knows what to do here—it infers the host (like https://acme.org
), grabs the image, and displays it.
But here’s the kicker: Email clients don’t know your host. Instead of seeing a nice image, your users see... nothing. Because /_next/static/[hash].png
doesn’t resolve to a valid resource, it doesn’t know what host the image should come from. Essentially, there's no complete URL that points to a real resource on the internet, so the image breaks.
What's the Fix?
Simple: Use a CDN. Upload the images you need for emails to a content delivery network (CDN) like Cloudinary. Then, reference the generated URL in your emails. Here's how it looks:
import { Img } from '@react-email/components';
export default function Page() {
return (
<Img
src="https://cdn.acme.org/email-image.png"
// Other props...
/>
);
}
Boom! Now the email client knows where to find your image. Crisis averted.
Hosting Your Entire public
Directory on a CDN
If you want to be thorough (or you're loading many images), you can configure Next.js to serve all your static assets from a CDN.
Here's how you'd set it up in your next.config.ts
:
import type { NextConfig } from 'next';
import { PHASE_DEVELOPMENT_SERVER } from 'next/constants';
export default (phase) => {
const isDev = phase === PHASE_DEVELOPMENT_SERVER;
const nextConfig: NextConfig = {
assetPrefix: isDev ? undefined : 'https://cdn.acme.org',
};
return nextConfig;
};
This tells Next.js to prepend the CDN's URL to all static files in production. For more details, you can check out the official Next.js documentation.
Now, when you use an image in your email component like this:
import { Img } from '@react-email/components';
import img from '../email-image.png';
export default function Page() {
return (
<Img
src={img}
// Other props...
/>
);
}
It’ll automatically generate a URL like https://cdn.acme.org/_next/static/[hash].png
, and your email images will load without a hitch.