Implementing Dynamic OG Images in a React/Vite Architecture on Vercel
5 min read
Learn how to troubleshoot and adapt non-native tools like Vercel/OG when integrating them into a React + Vite setup.
By Nikolina Požega
📦 Setting up the vercel/og package
I started by following the official Vercel OG documentation.
The basic example for generating an OG image looks like this:
import { ImageResponse } from '@vercel/og';
export default async function handler() {
return new ImageResponse(
(
<div
style={{
fontSize: 40,
color: 'black',
background: 'white',
width: '100%',
height: '100%',
padding: '50px 200px',
textAlign: 'center',
justifyContent: 'center',
alignItems: 'center',
}} >
👋 Hello
</div>
),
{ width: 1200, height: 630 }
);
}
Then I wanted to make it dynamic --- by passing parameters like ?title= in the URL:
import { ImageResponse } from '@vercel/og';
export const config = { runtime: 'edge' };
export default function handler(request) {
const { searchParams } = new URL(request.url);
const title = searchParams.get('title') || 'My default title';
return new ImageResponse(
(
<div
style={{
display: 'flex',
background: 'black',
color: 'white',
justifyContent: 'center',
alignItems: 'center',
width: '100%',
height: '100%'
}}>
<h1>{title}</h1>
</div>
),
{ width: 1200, height: 630 }
);
}
So far, everything looked fine... until the first build.
🧨 The first error: Unsupported module
Error: The Edge Function "api/ogImage.edge" is referencing unsupported modules: vcns\_\_/0/api/ogImage.edge.js: @vercel/og
After some digging, I stumbled upon a discussion in the Vercel Community forum.
The key takeaway? It's important how you define the runtime.
The docs use:
export const config = { runtime: 'edge' };
But the forum suggested changing it to:
export const runtime = 'edge';
That tiny change solved the first issue. ...and immediately created a new one.
⚙️ JSX not recognized
The next error came in loud and clear:
ERROR: The JSX syntax extension is not currently enabled
I had a feeling the issue was related to JSX not being compiled, but after being buried in errors and failed builds, I wasn’t entirely sure.
Adding .jsx as the file extension didn't help.
Neither did changing the runtime to Node.
Vercel expects Edge-compatible modules, and that means ESM, not CommonJS.
🧩 Understanding modules and exports
Here's where I hit the next learning curve.
I had to carefully distinguish between:
-
CJS (
require,module.exports) -
ESM (
import,export default)
Adding "type": "module" in package.json seemed like a fix, but I knew it would likely break my other server functions written in ES6 syntax — so I didn’t even bother trying it.
After trial and error, I realized:
-
I needed a
.mjsfile extension -
I needed to use ESM syntax
-
And most importantly, I had to export function in a way compatible with the Edge runtime
-
Import runtime properly
The correct form turned out to be:
export default { async fetch(req) { // logic here }, };
This replaced the traditional:
export default async function handler(req, res) {}
That subtle change finally got the function recognized properly by Vercel.
💥 JSX strikes again
Just when I thought I was done...
Vercel threw this one:
SyntaxError: Unexpected token '<'
This happens when the runtime tries to execute JSX without compilation.
Edge runtime doesn't know what to do with <div>...</div> unless it's been transformed into React.createElement() calls.
So, I ditched JSX entirely and rewrote my OG template like this:
const OgImageTemplate = React.createElement(
'div',
{
style: {
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
alignItems: 'flex-start',
width: '1200px',
height: '630px',
backgroundColor: '#2c1a4b',
color: '#D9D9D9',
fontFamily: 'Roboto Light',
padding: '80px 100px',
position: 'relative',
},
},
React.createElement('div', { style: { fontSize: 72, fontWeight: 800 } }, title)
);
And that was it. The magic moment. I opened the deployed URL and - it worked. My OG image appeared, beautifully rendered and dynamically generated.

🕺 The celebration moment
The moment that image loaded, I literally jumped up and started dancing around my room. I felt such a burst of energy that I recorded a short dance video because I wanted to share the feeling with the world. See it on the link below.
🧠 Lessons learned
If you're using vercel/og in a React/Vite setup (not Next.js), here's your checklist:
-
Know your module system (CJS vs. ESM)
-
Define
export const runtime = 'edge' -
Don't use JSX in
.mjsfiles --- useReact.createElementinstead -
Use the correct Edge-style export:
export default { async fetch(req) { ... } } -
Test each step carefully before adding complexity
-
Don't forget to add route in vercel configuration
⚠️ Bonus issue --- HTML entities
One more small curiosity I noticed later:
Some encoded characters (like apostrophes or quotes) appear fine in the Vercel preview but show up incorrectly on platforms like Facebook or LinkedIn.
For example:
ogTitle: 'Let's Talk DevOps: Get in Touch with VibeIT Experts',
Vercel might display this correctly, but social debuggers parse it literally, showing Let's instead of Let's.

This is just one example of the kind of issues that still await fixing—despite all the time already spent on implementation.
💡 Final thoughts
The whole process reminded me that before you can even start working on the fun part --- like visual design --- you often need to solve invisible, structural issues first.
That's what software development really is: making all the invisible things work just right, so that the visible ones can shine.
