It was a few days ago that I hit a wall. It was not a complicated architectural issue, it was not a cryptic bug in the back end, no, I had been stumped by a single thing a file, an image to be precise. An OpenGraph image, as it happens. The very thing that one would think should be so simple, so "set it and forget it."
And there I was, looking at a jumbled mass of text that was supposed to be a beautifully rendered Arabic certificate. My terminal did not have any errors shouting at me, but the output was completely broken. Classic lumbering moments that make you ask yourself a humbling question, "You're an engineer. How can a single image make you feel like a hack?"
The challenge was clear: generate dynamic OpenGraph images in a Next.js application with ImageResponse
, but with full, proper support for Arabic. Simple, right?
As it turns out, not quite.
The Problem: When "Right-to-Left" Goes Wrong
Generating a plain English image with ImageResponse
is straightforward. You write some JSX-like code, style it, and Next.js handles the rest. But the moment you introduce a right-to-left (RTL) language like Arabic, you enter a new dimension of complexity.
My initial attempts were a comedy of errors:
Fonts wouldn't load, or if they did, they'd render as generic, misaligned blocks (the infamous "tofu").
Text direction was a disaster. Sentences that should read right-to-left were broken, with words appearing in the wrong order.
Mixing Arabic with LTR text (like a username or a URL) created a chaotic blend of directions, making the final image look unprofessional and unreadable.
The core issue is that server-side image generation environments are not browsers. They don't inherently understand the nuances of font rendering, ligatures, and bidirectional text unless you explicitly tell them how.
The Breakthrough: Engineering a Solution, Not Hoping for One
After peeling back the layers of failure, I realized I needed to stop "hoping" a solution would work and start engineering one with precision. The fix involved a few key steps that, when combined, solved the puzzle completely.
1. Loading the Right Font, the Right Way
First things first: you need a font that actually supports Arabic glyphs. Noto Sans Arabic from Google Fonts is an excellent, open-source choice. The trick isn't just picking the font, but how you load it. ImageResponse
requires the font data as an ArrayBuffer
.
You can fetch the font directly from the Google Fonts CDN and convert it.
TypeScript
// In your route handler (e.g., /api/og)
const arabicFont = await fetch(
"https://fonts.gstatic.com/s/notosansarabic/v18/nwpxtLGrOAZMl5nJ_wfgRg3DrWFZWsnVBJ_sS6tlqHHFlhQ5l3sQWIHPqzCfyGyvu3CBFQLaig.ttf",
).then(res => res.arrayBuffer());
By fetching the font file and passing its ArrayBuffer
to ImageResponse
, we ensure the rendering engine has everything it needs to draw the characters correctly.
2. A Function for RTL Word Order
One of the most frustrating parts of dealing with Arabic text in programming environments is that string manipulations can wreck the word order. A simple split(' ')
and join(' ')
can inadvertently reverse a sentence that was already correct.
To counter this, I wrote a small utility function to handle strings that I knew had to be RTL. It reverses the order of words to ensure they display correctly in the final image. It even handles hyphenated words, which was a specific edge case for my usernames.
TypeScript
function processArabicName(str: string): string {
const words = str.trim().split(" ");
const reversedWords = words.reverse().map(word => {
if (word.includes("-")) {
const parts = word.split("-");
return parts.reverse().join("-");
}
return word;
});
return reversedWords.join(" ");
}
Wait, are you reversing an already reversed language? Yes, and it feels counterintuitive. But in some rendering contexts, you need to "pre-correct" the order of words in a string before the RTL direction style is applied, so the final visual output is what you expect.
3. Direction Where It Matters
This was the final and most crucial piece. Instead of applying a global direction: "rtl"
to the entire image and hoping for the best, I applied directionality with surgical precision.
The main container remains direction-agnostic.
Arabic text blocks get
direction: "rtl"
.English or user-generated content (like a username) gets
direction: "ltr"
to prevent it from being incorrectly reversed.
Here’s how it looks in practice:
TypeScript
// Inside the ImageResponse JSX:
// An Arabic title, styled RTL
<div
style={{
fontSize: "40px",
textAlign: "center",
direction: "rtl", // Correct for Arabic text
}}
>
الشهادة
</div>
// A dynamic username, explicitly styled LTR
<div
style={{
fontSize: "24px",
fontWeight: "bold",
direction: "ltr", // Keep LTR for names, URLs, etc.
}}
>
{decodeURIComponent(username)}
</div>
By isolating the directional context, we can seamlessly blend Arabic and English content in the same image without breaking the layout. The result is clean, predictable, and correct.
The Code That Speaks for Itself (with the generated Opengraph Image)
Putting it all together, we have a robust system for generating beautiful, dynamic OpenGraph images that respect the intricacies of the Arabic language.
Here is a simplified look at the final ImageResponse
structure:
TypeScript
return new ImageResponse(
(
<div style={{ /* ... Outer container styles ... */ }}>
{/* ... Certificate frame and decor ... */}
<div style={{ direction: "rtl", textAlign: "center" }}>
{processArabicName("شهادة إنجاز")}
</div>
<div style={{ direction: "ltr", textAlign: "center" }}>
{decodeURIComponent(username)}
</div>
<div style={{ direction: "rtl", textAlign: "center", lineHeight: "2.4" }}>
درجة على بنجاح حصل قد
</div>
{/* ... More elements ... */}
</div>
),
{
width: 1200,
height: 630,
fonts: [
{
name: "Noto Sans Arabic",
data: arabicFont,
style: "normal",
},
],
},
);
After implementing the code, the true test is seeing it in action. I confirmed the image generation works perfectly by running a URL through Facebook's Sharing Debugger tool, which simulates how Meta's crawler fetches the page and its OpenGraph tags.

The thing I love most about this solution is that it's not a hack. It's a proper engineering fix from understanding how the problem occurred. You don't have to cross your fingers. You just have to be specific.
If you are developing multi-lingual applications, particularly for SaaS, or even just a lot of web or mobile content, you have to get your OpenGraph images right. This is what people see first when you share a link. Yes, you can add Arabic support, but it does take a bit more care than the docs make it out to be.
Have a similar problem? Feel free to reach out.
I'm always happy to share what I've learned. Happy coding!