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 .mjs file 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.

Vercel Open Graph Image


🕺 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.

Successful deploy celebration


🧠 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 .mjs files --- use React.createElement instead

  • 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&#39;s Talk DevOps: Get in Touch with VibeIT Experts',

Vercel might display this correctly, but social debuggers parse it literally, showing Let&#39;s instead of Let's.

Facebook Debugger Parsing

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.

Vercel's Open Graph

Nikolina Požega

Nikolina Požega

Nikolina is a passionate front-end developer and a technical writer with a focus on problem-solving and debugging. She loves sharing her knowledge with the community.