Codegea

Development

Custom software developmentSystems, platforms and integrations built around your operationCorporate websitesProfessional, fast and built to inspire trustOnline storesCatalog, payments and shipping ready to sell 24/7WooCommerceFast WooCommerce stores with each country’s payments and invoicingLanding pagesA page focused on one goal: turning visitors into customersMobile appsCustom iOS and Android apps that put your business in your customers’ pockets

Integration and CMS

CMS integrationsWordPress, Strapi, Directus and Arc XP connected to your whole ecosystemArc XPSupport, implementation and integrations for the media platformMigrationsMigrate your site, platform or database without losing anything

Growth and infrastructure

AI automationTechnology does the repetitive work and your team focuses on what mattersOn-page SEOBe found and recommended by Google: technical and content optimizationServers & hostingFast, secure servers monitored 24/7
RetainerYour dev team, by the monthFrom 20h a month. Fixed fee.
An AI quoting tool to quote services in minutesProfessional quotes in minutes, with projects, sellers and payments tracked in one place.See the case

By type

EcommerceStores and catalogsCorporate sitesBrand and lead genPlatforms & appsCustom software

Explore

All projectsCases and resultsClientsWho trusts usRetainerMonthly dev team

Blog

All articlesGuides and opinionNewsletterOne email a monthContactWe reply in 24h

Latest articles

How codegea.com is built: Next.js, Payload, Turso and R2CMS · 2026-10-20Cloudflare Workers: what they are and when to use themNode.js · 2026-10-19Live Preview in Payload with Next.jsCMS · 2026-10-19
NewsletterJoin our newsletterPractical web, SEO and AI notes.
ServicesCustom software developmentCorporate websitesOnline storesWooCommerceLanding pagesMobile appsCMS integrationsArc XPMigrationsAI automationOn-page SEOServers & hostingYour dev team, by the month
ProjectsProfessional quotes in minutes, with projects, sellers and payments tracked in one place.EcommerceCorporate sitesPlatforms & appsAll projectsClientsRetainer
Retainer
BlogAll articlesNewsletterContactHow codegea.com is built: Next.js, Payload, Turso and R2Cloudflare Workers: what they are and when to use themLive Preview in Payload with Next.jsJoin our newsletter
ESEN
Let's talk

Web

How to generate share images (OG) with code

Generate the image shown when a link is shared without opening a design editor: next/og, brand fonts and a script that creates one per article and per language.

Derian Pinto · Founder & Full Stack Developer2026-10-183 min

When you share a link on WhatsApp, LinkedIn or X, the network shows an image. If you don't define one, it shows something random or nothing. Making them by hand for every article is a chore nobody keeps up. We generate them with code: one per article and per language, with the headline inside and the brand.

That image is the page's og:image. It is no longer shown inside the article, where it repeated the title right above it, but it is still what people see when they share the link.

The tools

  • next/og: the ImageResponse class, which turns a tree of React elements into an image. Under the hood it uses Satori.
  • sharp: to convert that image to WebP and compress it.
  • A script that walks the articles and writes the files.

You could use Next.js's special opengraph-image file, which generates the image when it is requested. We chose a script for a practical reason: this way the image is a file, it is uploaded to the CMS media library and can be reused as meta.image, with no computation on every request.

Step 1: the design is a component

The cover is a React component with inline styles. A minimal version:

function Cover({ title }: { title: string }) {
  return (
    <div
      style={{
        width: '100%',
        height: '100%',
        display: 'flex',
        flexDirection: 'column',
        justifyContent: 'space-between',
        padding: '80px 96px',
        background: '#0B0F1A',
        color: '#EEF1F7',
      }}
    >
      <span style={{ fontFamily: 'Mono', fontSize: 28 }}>codegea</span>
      <div style={{ fontFamily: 'Sans', fontWeight: 500, fontSize: 112 }}>{title}</div>
    </div>
  );
}

Satori understands a subset of CSS: layout with flexbox, no grid and not many modern properties. If something doesn't show, it is almost always because a display: 'flex' is missing.

Step 2: fonts

Satori doesn't use system fonts: you have to pass it the files. We download the three brand fonts once and store them in a cache folder:

const fonts = await loadFonts(CACHE);
// [{ name: 'Sans', data, weight: 500 }, { name: 'Serif', … }, { name: 'Mono', … }]

Then you hand them to ImageResponse in the fonts option and use them by name in fontFamily.

Step 3: from component to file

const res = new ImageResponse(<Cover title={title} />, {
  width: 1600,
  height: 900,
  fonts,
});
const png = Buffer.from(await res.arrayBuffer());
await sharp(png).webp({ quality: 84 }).toFile(`covers/${slug}.${locale}.webp`);

One detail: Satori generates a PNG, which is quite heavy. Running it through sharp to WebP leaves it at a fraction of the size. We use 1600×900; for social networks a landscape format with a ratio near 1.91:1 is recommended (for example, 1200×630). If you prefer that format, change the dimensions.

Step 4: the headline with a highlighted word

The site's headlines mark one word with asterisks to show it in serif italic: How to generate *share images* with code. A function splits the text into normal and highlighted words, and the component applies another style to the latter:

{words(title).map((w, i) => (
  <span key={i} style={w.em ? { fontFamily: 'Serif', fontStyle: 'italic', color: '#5C7CF5' } : {}}>
    {w.text}
  </span>
))}

Step 5: one per article and per language

The script reads each article's frontmatter, takes coverTitle (or title if there isn't one), calculates the reading minutes from the body and generates the image for each language. Then it writes the file path into the article itself:

pnpm covers                # only the ones without a cover
pnpm covers my-article     # regenerate one, in es and en
pnpm covers --force        # all of them

Since each image carries the headline inside, there is one per language; the same one doesn't work for the Spanish and English article.

What we learned

  • Long text gets cut off. We adjust the font size depending on how many characters the headline has. Without that, a long title spills out of the frame.
  • Test the image on the networks. Each network caches the previous image; to see the change you have to ask them to re-read the link with their debugging tools.
  • Don't use third-party photos or other people's logos. A branded image with typography is enough and avoids rights problems.

To see how they connect with the rest of the site, look at how codegea.com is built, and to review the rest of your pages' SEO, titles and meta descriptions that raise CTR.

Share this article?LinkedInWhatsAppX

Written by

Derian Pinto

Founder & Full Stack Developer

Founder of Codegea. Builds custom websites, stores and platforms, and integrates and supports Arc XP for media companies.

Join our newsletter

An email with news and practical web, SEO and AI notes. No spam, unsubscribe anytime.

Shall we build your project?

Need help? Chat with us