<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Corbanware</title>
    <description>The latest articles on DEV Community by Corbanware (@corbanware).</description>
    <link>https://dev.to/corbanware</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3773444%2Faf1e64c2-072e-4647-b4df-a2b0da0dc2fc.jpg</url>
      <title>DEV Community: Corbanware</title>
      <link>https://dev.to/corbanware</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/corbanware"/>
    <language>en</language>
    <item>
      <title>How to Add OG Images to Next.js in 5 Minutes</title>
      <dc:creator>Corbanware</dc:creator>
      <pubDate>Mon, 27 Apr 2026 01:01:38 +0000</pubDate>
      <link>https://dev.to/corbanware/how-to-add-og-images-to-nextjs-in-5-minutes-45nj</link>
      <guid>https://dev.to/corbanware/how-to-add-og-images-to-nextjs-in-5-minutes-45nj</guid>
      <description>&lt;h1&gt;
  
  
  How to Add OG Images to Next.js in 5 Minutes
&lt;/h1&gt;

&lt;p&gt;If you have ever shared a link on Twitter or Slack and watched it render as a sad, plain-text URL with no preview image, you already understand why OG images matter. Those 1200x630 pixel cards are the difference between a link that gets clicked and one that gets scrolled past. Studies show that links with rich preview images get 2-3x more engagement than bare text links.&lt;/p&gt;

&lt;p&gt;The good news: if you are building with Next.js and the App Router, adding OG images is remarkably straightforward. Let me walk you through three approaches, from the simplest to the most powerful.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Foundation: Understanding OG Meta Tags
&lt;/h2&gt;

&lt;p&gt;Before we touch any Next.js code, here is what the browser needs to see in your HTML &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:image"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"https://yoursite.com/og.png"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:image:width"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"1200"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:image:height"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"630"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:title"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"Your Page Title"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:description"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"A brief description"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is it. Every social platform (Facebook, Twitter/X, LinkedIn, Slack, Discord, WhatsApp) reads these tags to build the preview card. The image URL &lt;strong&gt;must&lt;/strong&gt; be absolute -- relative paths will not work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Approach 1: Static Metadata (30 Seconds)
&lt;/h2&gt;

&lt;p&gt;For pages where the OG image never changes (your homepage, about page, pricing page), use the &lt;code&gt;metadata&lt;/code&gt; export in your layout or page file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/layout.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Metadata&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;My Awesome App&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The best thing since sliced bread&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;openGraph&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;My Awesome App&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The best thing since sliced bread&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;images&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://yoursite.com/og-homepage.png&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;630&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;My Awesome App - homepage preview&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;twitter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;card&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;summary_large_image&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next.js automatically renders the correct &lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; tags in the HTML head. No manual string concatenation, no &lt;code&gt;dangerouslySetInnerHTML&lt;/code&gt;. It even handles the Twitter card fallback for you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pro tip:&lt;/strong&gt; Always include the &lt;code&gt;twitter.card: "summary_large_image"&lt;/code&gt; property. Without it, Twitter defaults to the small square card, which wastes most of your carefully designed image.&lt;/p&gt;

&lt;h2&gt;
  
  
  Approach 2: Dynamic generateMetadata (2 Minutes)
&lt;/h2&gt;

&lt;p&gt;This is where things get interesting. For blogs, documentation, or any page with dynamic content, you want a unique OG image per page. The &lt;code&gt;generateMetadata&lt;/code&gt; async function lets you compute metadata at request time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/blog/[slug]/page.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Metadata&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getPostBySlug&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/lib/posts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateMetadata&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Metadata&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;slug&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getPostBySlug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;excerpt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;openGraph&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;excerpt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;article&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;publishedTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publishedAt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;images&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`https://yoursite.com/api/og?title=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;630&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;twitter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;card&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;summary_large_image&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the OG image URL points to an API route. That brings us to the next question: how do you actually &lt;strong&gt;generate&lt;/strong&gt; the image?&lt;/p&gt;

&lt;h2&gt;
  
  
  Approach 3: Generating Images on the Fly
&lt;/h2&gt;

&lt;p&gt;You have two main options here.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option A: @vercel/og (Self-Hosted)
&lt;/h3&gt;

&lt;p&gt;Vercel's @vercel/og library lets you write JSX that renders as an image. It uses Satori under the hood -- a layout engine that converts React components to SVG, then rasterizes to PNG.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/api/og/route.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ImageResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/og&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;runtime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;edge&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;searchParams&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;My Blog&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ImageResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;100%&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;100%&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;flex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;flexDirection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;column&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;justifyContent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;center&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;alignItems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;center&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;linear-gradient(135deg, #667eea 0%, #764ba2 100%)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;white&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fontWeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;textAlign&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;center&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;630&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works well if you want full control over the design and are comfortable with Satori's CSS limitations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option B: External API
&lt;/h3&gt;

&lt;p&gt;If you would rather not maintain your own image generation endpoint, an external API simplifies things significantly. For example, with &lt;a href="https://ogimg.xyz?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=wf5" rel="noopener noreferrer"&gt;ogimg.xyz&lt;/a&gt;, your dynamic metadata becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ogUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://ogimg.xyz/api/og&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="nx"&gt;utm_source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;devto&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;utm_medium&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;utm_campaign&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;wf5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;ogUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;ogUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;excerpt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;ogUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;template&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gradient&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No API route to maintain, no font files to bundle, no Satori quirks to debug. ogimg.xyz offers a free tier of 50 images/month, which is enough for most personal blogs and small projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing Your OG Images
&lt;/h2&gt;

&lt;p&gt;After deploying, always verify your images actually work:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Facebook Sharing Debugger&lt;/strong&gt; -- Paste your URL and hit Scrape Again to force a fresh fetch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Twitter Card Validator&lt;/strong&gt; -- Preview exactly what Twitter will render.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LinkedIn Post Inspector&lt;/strong&gt; -- Check how LinkedIn will render your shared link.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open your browser DevTools&lt;/strong&gt; -- Search the HTML head for og:image and make sure the URL loads.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Quick Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;og:image URL is absolute (starts with https://)&lt;/li&gt;
&lt;li&gt;Image dimensions are 1200x630&lt;/li&gt;
&lt;li&gt;File size is under 1MB (5MB max for most platforms)&lt;/li&gt;
&lt;li&gt;twitter:card is set to summary_large_image&lt;/li&gt;
&lt;li&gt;Image renders correctly when you open the URL directly&lt;/li&gt;
&lt;li&gt;You have tested with at least one social platform debugger&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Adding proper OG images to a Next.js app is one of those 5-minute tasks that pays dividends every time someone shares your content. Whether you go with static metadata, dynamic generateMetadata, self-hosted @vercel/og, or an external service, the important thing is that you do it.&lt;/p&gt;

&lt;p&gt;Your links deserve better than plain text.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have questions about OG images in Next.js? Drop a comment below -- I read every one.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>GEO vs SEO: Why Optimizing for AI Search Is the Next Frontier</title>
      <dc:creator>Corbanware</dc:creator>
      <pubDate>Mon, 20 Apr 2026 01:00:50 +0000</pubDate>
      <link>https://dev.to/corbanware/geo-vs-seo-why-optimizing-for-ai-search-is-the-next-frontier-13f</link>
      <guid>https://dev.to/corbanware/geo-vs-seo-why-optimizing-for-ai-search-is-the-next-frontier-13f</guid>
      <description></description>
      <category>webdev</category>
      <category>ai</category>
      <category>saas</category>
      <category>programming</category>
    </item>
    <item>
      <title>Why Your Brand Is Invisible to ChatGPT (And How to Fix It)</title>
      <dc:creator>Corbanware</dc:creator>
      <pubDate>Mon, 13 Apr 2026 01:00:49 +0000</pubDate>
      <link>https://dev.to/corbanware/why-your-brand-is-invisible-to-chatgpt-and-how-to-fix-it-2721</link>
      <guid>https://dev.to/corbanware/why-your-brand-is-invisible-to-chatgpt-and-how-to-fix-it-2721</guid>
      <description></description>
      <category>webdev</category>
      <category>ai</category>
      <category>saas</category>
      <category>programming</category>
    </item>
    <item>
      <title>I Built an AI Search Visibility Auditor - Here Is What I Learned</title>
      <dc:creator>Corbanware</dc:creator>
      <pubDate>Mon, 06 Apr 2026 01:00:26 +0000</pubDate>
      <link>https://dev.to/corbanware/i-built-an-ai-search-visibility-auditor-here-is-what-i-learned-3j85</link>
      <guid>https://dev.to/corbanware/i-built-an-ai-search-visibility-auditor-here-is-what-i-learned-3j85</guid>
      <description></description>
      <category>webdev</category>
      <category>ai</category>
      <category>saas</category>
      <category>programming</category>
    </item>
    <item>
      <title>GEO vs SEO: Why Optimizing for AI Search Is the Next Frontier</title>
      <dc:creator>Corbanware</dc:creator>
      <pubDate>Mon, 30 Mar 2026 01:00:36 +0000</pubDate>
      <link>https://dev.to/corbanware/geo-vs-seo-why-optimizing-for-ai-search-is-the-next-frontier-254d</link>
      <guid>https://dev.to/corbanware/geo-vs-seo-why-optimizing-for-ai-search-is-the-next-frontier-254d</guid>
      <description></description>
      <category>webdev</category>
      <category>ai</category>
      <category>saas</category>
      <category>programming</category>
    </item>
    <item>
      <title>Why Your Brand Is Invisible to ChatGPT (And How to Fix It)</title>
      <dc:creator>Corbanware</dc:creator>
      <pubDate>Mon, 23 Mar 2026 01:00:31 +0000</pubDate>
      <link>https://dev.to/corbanware/why-your-brand-is-invisible-to-chatgpt-and-how-to-fix-it-3d25</link>
      <guid>https://dev.to/corbanware/why-your-brand-is-invisible-to-chatgpt-and-how-to-fix-it-3d25</guid>
      <description></description>
      <category>webdev</category>
      <category>ai</category>
      <category>saas</category>
      <category>programming</category>
    </item>
    <item>
      <title>I Built an AI Search Visibility Auditor - Here Is What I Learned</title>
      <dc:creator>Corbanware</dc:creator>
      <pubDate>Mon, 16 Mar 2026 01:01:11 +0000</pubDate>
      <link>https://dev.to/corbanware/i-built-an-ai-search-visibility-auditor-here-is-what-i-learned-1c58</link>
      <guid>https://dev.to/corbanware/i-built-an-ai-search-visibility-auditor-here-is-what-i-learned-1c58</guid>
      <description></description>
      <category>webdev</category>
      <category>ai</category>
      <category>saas</category>
      <category>programming</category>
    </item>
    <item>
      <title>Building a Multi-LLM Fallback System with Vercel AI SDK</title>
      <dc:creator>Corbanware</dc:creator>
      <pubDate>Mon, 09 Mar 2026 01:01:20 +0000</pubDate>
      <link>https://dev.to/corbanware/building-a-multi-llm-fallback-system-with-vercel-ai-sdk-1ijo</link>
      <guid>https://dev.to/corbanware/building-a-multi-llm-fallback-system-with-vercel-ai-sdk-1ijo</guid>
      <description>&lt;p&gt;A practical guide to implementing LLM provider fallback chains for production applications. Covers the architecture of Geonapse's 3-tier fallback system (Gemini Flash-Lite ? Groq ? DeepSeek), error handling patterns, cost optimization strategies, response quality validation, and monitoring. Includes code examples using Vercel AI SDK's unified interface and lessons learned from running multi-provider LLM systems in production.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>saas</category>
      <category>programming</category>
    </item>
    <item>
      <title>GEO vs SEO: Why Your Website Is Invisible to ChatGPT (And How to Fix It)</title>
      <dc:creator>Corbanware</dc:creator>
      <pubDate>Mon, 02 Mar 2026 01:01:17 +0000</pubDate>
      <link>https://dev.to/corbanware/geo-vs-seo-why-your-website-is-invisible-to-chatgpt-and-how-to-fix-it-57pi</link>
      <guid>https://dev.to/corbanware/geo-vs-seo-why-your-website-is-invisible-to-chatgpt-and-how-to-fix-it-57pi</guid>
      <description>&lt;p&gt;Generative Engine Optimization (GEO) is emerging as the next evolution of SEO. This article explores why traditional SEO techniques only partially work for AI search engines, what factors influence LLM brand recommendations, and a practical framework for auditing your AI search visibility. Covers knowledge graph presence, structured data importance, citation cascades, content structure optimization, and the growing importance of platform presence across Wikipedia, Crunchbase, G2, and other knowledge sources.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>saas</category>
      <category>programming</category>
    </item>
    <item>
      <title>I Built an AI Search Visibility Auditor Here's the Architecture</title>
      <dc:creator>Corbanware</dc:creator>
      <pubDate>Mon, 23 Feb 2026 01:01:07 +0000</pubDate>
      <link>https://dev.to/corbanware/i-built-an-ai-search-visibility-auditor-heres-the-architecture-7fb</link>
      <guid>https://dev.to/corbanware/i-built-an-ai-search-visibility-auditor-heres-the-architecture-7fb</guid>
      <description>&lt;p&gt;A technical deep-dive into building Geonapse, an AI search visibility auditor. Covers the 3-tier LLM fallback architecture (Gemini Flash-Lite ? Groq ? DeepSeek), the 40+ check audit engine, ranking detection via AI search APIs, and platform presence scanning across 8 knowledge platforms. Built with Next.js 16, Vercel AI SDK, Neon PostgreSQL, and Drizzle ORM. Includes lessons learned about LLM reliability, cost optimization, and building evaluation frameworks for AI-generated content.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>saas</category>
      <category>programming</category>
    </item>
    <item>
      <title>I Built an OG Image Generator API — Here's How (Next.js + Vercel Edge)</title>
      <dc:creator>Corbanware</dc:creator>
      <pubDate>Sun, 15 Feb 2026 13:56:47 +0000</pubDate>
      <link>https://dev.to/corbanware/i-built-an-og-image-generator-api-heres-how-nextjs-vercel-edge-1dg2</link>
      <guid>https://dev.to/corbanware/i-built-an-og-image-generator-api-heres-how-nextjs-vercel-edge-1dg2</guid>
      <description>&lt;p&gt;Every web page that gets shared on social media needs an Open Graph image. It's that 1200x630 preview card you see on Twitter, Facebook, LinkedIn, and Slack. Without one, your shared links look like plain text. With a good one, click-through rates go up significantly.&lt;/p&gt;

&lt;p&gt;I've been building web projects for years, and creating OG images has always been my least favorite part of the process. Open Figma, create a canvas, add text, pick colors, export, upload. Repeat for every page. For a blog with 30 posts, that's 30 images. For a documentation site with 100 pages, it's simply not viable.&lt;/p&gt;

&lt;p&gt;So I built &lt;a href="https://ogimg.xyz?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=technical_article" rel="noopener noreferrer"&gt;ogimg.xyz&lt;/a&gt; — an API that generates OG images programmatically. Send a POST request, get back a PNG. Here's how it works under the hood.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Architecture
&lt;/h3&gt;

&lt;p&gt;The entire application is a Next.js 15 project deployed on Vercel. The image generation endpoint is a Vercel Edge Function, which means it runs on Cloudflare's network close to the user — no cold starts, no long-running server processes.&lt;/p&gt;

&lt;p&gt;The core pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request → Validate API Key → Resolve Template → Satori (JSX → SVG) → Resvg (SVG → PNG) → Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let me break down each step.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Request Validation
&lt;/h3&gt;

&lt;p&gt;Every API call requires an API key passed via the &lt;code&gt;Authorization: Bearer&lt;/code&gt; header. The key is looked up in a Neon PostgreSQL database (via Drizzle ORM). I check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the key valid and active?&lt;/li&gt;
&lt;li&gt;What plan tier is this user on?&lt;/li&gt;
&lt;li&gt;Have they exceeded their monthly quota?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rate limiting uses a sliding window counter. Free users get 50 images/month, Hobby gets 1,000, and Pro gets 10,000.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://ogimg.xyz/api/generate?utm_source&lt;span class="o"&gt;=&lt;/span&gt;devto&amp;amp;utm_medium&lt;span class="o"&gt;=&lt;/span&gt;article&amp;amp;utm_campaign&lt;span class="o"&gt;=&lt;/span&gt;wf5 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer og_live_abc123..."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "title": "My Awesome Blog Post",
    "description": "Learn how to build better software",
    "template": "gradient",
    "backgroundColor": "#0f172a",
    "textColor": "#f8fafc"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: The Template System
&lt;/h3&gt;

&lt;p&gt;Each template is a React component. Yes, actual JSX — because the rendering engine (Satori) understands React components.&lt;/p&gt;

&lt;p&gt;Here's a simplified version of what a template looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;GradientTemplate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;textColor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pattern&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;TemplateProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;630&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;flex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;flexDirection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;column&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;justifyContent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;center&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`linear-gradient(135deg, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;adjustColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;textColor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;pattern&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PatternOverlay&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;56&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fontWeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;lineHeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.2&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;marginTop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are 10 templates total: gradient, minimal, bold, split, centered, documentation, dark, light, branded, and announcement. Each is designed for a different use case.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Background Patterns
&lt;/h3&gt;

&lt;p&gt;I built 10 SVG-based background patterns: dots, grid, diagonal lines, cross-hatch, waves, circles, hexagons, triangles, diamonds, and noise. They're rendered as inline SVG within the template and can be customized with color and opacity.&lt;/p&gt;

&lt;p&gt;The pattern is layered behind the text content using Flexbox positioning (Satori doesn't support &lt;code&gt;position: absolute&lt;/code&gt;, so everything is Flexbox-based).&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Satori — JSX to SVG
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/vercel/satori" rel="noopener noreferrer"&gt;Satori&lt;/a&gt; is Vercel's library for converting React components (JSX) to SVG. It implements a subset of CSS Flexbox and renders text using actual font files (loaded as ArrayBuffers).&lt;/p&gt;

&lt;p&gt;Key limitations I had to work around:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No &lt;code&gt;position: absolute&lt;/code&gt;&lt;/strong&gt;: All layout must use Flexbox. This makes some designs trickier but keeps the rendering engine simple and fast.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limited CSS properties&lt;/strong&gt;: No &lt;code&gt;box-shadow&lt;/code&gt;, no &lt;code&gt;backdrop-filter&lt;/code&gt;, no CSS gradients on text. You work within the constraints.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Font loading&lt;/strong&gt;: You must provide font files explicitly. I bundle Inter and a few other Google Fonts as static assets.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Despite the limitations, Satori is fast. It converts a template to SVG in about 50-100ms.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: SVG to PNG
&lt;/h3&gt;

&lt;p&gt;The SVG from Satori is converted to PNG using Resvg (via &lt;code&gt;@vercel/og&lt;/code&gt;). This is a Rust-based SVG renderer compiled to WebAssembly, so it runs on the Edge without any native dependencies. The PNG conversion takes another 50-150ms.&lt;/p&gt;

&lt;p&gt;The total pipeline from request to response is typically 200-500ms, depending on complexity and edge location.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6: The Watermark
&lt;/h3&gt;

&lt;p&gt;Free tier images get a small "Powered by ogimg.xyz" watermark in the bottom-right corner. It's rendered as part of the template — just an additional text element added conditionally based on the user's plan tier. Paid plans remove it entirely.&lt;/p&gt;

&lt;h3&gt;
  
  
  URL Auto-Fetch Mode
&lt;/h3&gt;

&lt;p&gt;There's a convenience feature where you send a URL instead of content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://ogimg.xyz/api/generate?utm_source&lt;span class="o"&gt;=&lt;/span&gt;devto&amp;amp;utm_medium&lt;span class="o"&gt;=&lt;/span&gt;article&amp;amp;utm_campaign&lt;span class="o"&gt;=&lt;/span&gt;wf5 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer og_live_abc123..."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "url": "https://example.com/blog/my-post",
    "template": "minimal"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The API fetches the URL, parses the HTML for &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;meta name="description"&amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;lt;link rel="icon"&amp;gt;&lt;/code&gt;, and uses those as the template input.&lt;/p&gt;

&lt;p&gt;Important security note: I implemented SSRF protection here. The fetch is restricted to public IP ranges only (no &lt;code&gt;127.0.0.1&lt;/code&gt;, no &lt;code&gt;10.x.x.x&lt;/code&gt;, no &lt;code&gt;169.254.x.x&lt;/code&gt;). Schemes are limited to &lt;code&gt;http&lt;/code&gt; and &lt;code&gt;https&lt;/code&gt;. There's a 5-second timeout. If you're building something similar that accepts user-provided URLs, don't skip this step.&lt;/p&gt;

&lt;h3&gt;
  
  
  Billing Integration
&lt;/h3&gt;

&lt;p&gt;I use &lt;a href="https://www.lemonsqueezy.com/" rel="noopener noreferrer"&gt;LemonSqueezy&lt;/a&gt; as the merchant of record. This means LemonSqueezy handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Payment processing (Stripe under the hood)&lt;/li&gt;
&lt;li&gt;Global tax calculation and remittance (VAT, GST, sales tax)&lt;/li&gt;
&lt;li&gt;Invoicing&lt;/li&gt;
&lt;li&gt;Subscription management&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On my end, I listen for LemonSqueezy webhooks to update user plan tiers in my database. When a user upgrades, their quota and feature access update immediately.&lt;/p&gt;

&lt;p&gt;Pricing tiers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plan&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;Images/Month&lt;/th&gt;
&lt;th&gt;Templates&lt;/th&gt;
&lt;th&gt;Watermark&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hobby&lt;/td&gt;
&lt;td&gt;$4.90/mo&lt;/td&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pro&lt;/td&gt;
&lt;td&gt;$9.90/mo&lt;/td&gt;
&lt;td&gt;10,000&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lifetime&lt;/td&gt;
&lt;td&gt;$149 once&lt;/td&gt;
&lt;td&gt;10,000&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  The Live Playground
&lt;/h3&gt;

&lt;p&gt;I built an interactive playground at &lt;a href="https://ogimg.xyz?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=technical_article" rel="noopener noreferrer"&gt;ogimg.xyz&lt;/a&gt; where you can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Select a template from a visual grid&lt;/li&gt;
&lt;li&gt;Enter your title and description&lt;/li&gt;
&lt;li&gt;Pick background colors and patterns&lt;/li&gt;
&lt;li&gt;See the preview update in real-time&lt;/li&gt;
&lt;li&gt;Copy the equivalent API call (curl or JavaScript)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This serves two purposes: it lets non-technical users generate images without writing code, and it lets developers experiment before integrating the API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lessons Learned
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Satori is great but limited.&lt;/strong&gt; If you need pixel-perfect designs, you'll hit its CSS limitations. For structured templates with text and simple graphics, it's perfect.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Edge rendering is the right call for this use case.&lt;/strong&gt; No cold starts, globally distributed, and fast enough for real-time generation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Freemium works for developer tools.&lt;/strong&gt; Developers need to test before they commit. A generous free tier with a clear upgrade path converts better than a free trial with a time limit.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;SSRF protection is essential.&lt;/strong&gt; If your API accepts user-provided URLs, protect against server-side request forgery from day one. Not later. Now.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Try It
&lt;/h3&gt;

&lt;p&gt;Head over to &lt;a href="https://ogimg.xyz?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=technical_article" rel="noopener noreferrer"&gt;ogimg.xyz&lt;/a&gt; and generate your first image. The free tier gives you 50 images/month with no credit card required. If you're integrating into a Next.js project, it's literally one fetch call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://ogimg.xyz/api/generate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,?&lt;/span&gt;&lt;span class="nx"&gt;utm_source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;devto&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;utm_medium&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;utm_campaign&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;wf5&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OGIMG_API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;My Page Title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;A brief description&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gradient&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#1e293b&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;imageBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arrayBuffer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// Save or serve the image&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'd love feedback on the API design, the templates, and anything else. You can find me on Twitter at &lt;a href="https://twitter.com/corbanware" rel="noopener noreferrer"&gt;@corbanware&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>webdev</category>
      <category>api</category>
      <category>saas</category>
    </item>
  </channel>
</rss>
