March 28, 2026 · 8 min read

OG Image API for Next.js, Remix & Astro

Every framework handles meta tags differently. Next.js has its Metadata API, Remix uses loader functions, and Astro leans on frontmatter and layouts. Here is how to integrate dynamic OG image generation into all three—with a single API, zero infrastructure, and under five minutes of setup per framework.

Why Framework-Native OG Images Matter

Social preview images drive clicks. A link shared on Twitter, LinkedIn, or Slack with a branded, readable card gets 2–3x more engagement than a bare URL or a generic fallback image. But generating those images at build time or with headless browsers adds complexity you do not need.

The better approach: point your og:image meta tag at an API endpoint that generates the image on demand. The social platform crawls your page, hits the URL, gets a PNG back. No build step. No Puppeteer. No image pipeline.

OGPeek gives you that endpoint. You pass parameters like title, subtitle, template, and brandColor via a URL, and the API returns a 1200×630 PNG in under 200ms. It works with any framework because it is just a URL—but each framework has its own idiomatic way to set meta tags. This guide covers the three most popular choices for modern web apps.

Example OG image generated by OGPeek for this article

The OGPeek API in 30 Seconds

Before diving into framework code, here is the basic idea. You construct a URL with your parameters:

https://ogpeek.com/api/v1/og
  ?title=Your+Page+Title
  &subtitle=A+short+description
  &template=gradient
  &theme=midnight
  &brandColor=%23FF7A00

That URL returns a PNG image. You can paste it in a browser to preview it, or use it as the value of your og:image meta tag. The free tier gives you 50 images per day with a small watermark. Paid plans ($9/mo for 5,000 images, $29/mo for 50,000) remove the watermark and unlock the POST endpoint with API key authentication.

For a deeper walkthrough of parameters, templates, and themes, see the complete OG image API guide.

Next.js (App Router)

Next.js 13+ introduced the App Router with a new generateMetadata function that replaces the old <Head> component. This is the cleanest way to set OG images because metadata is colocated with your route.

Static Pages

For a page with a fixed title, export a metadata object directly:

// app/about/page.tsx

export const metadata = {
  title: 'About Us',
  openGraph: {
    title: 'About Us',
    description: 'Learn about our team and mission.',
    images: [
      {
        url: 'https://ogpeek.com/api/v1/og'
          + '?title=About+Us'
          + '&subtitle=Our+Team+%26+Mission'
          + '&template=gradient'
          + '&theme=midnight'
          + '&brandColor=%230066FF',
        width: 1200,
        height: 630,
        alt: 'About Us',
      },
    ],
  },
  twitter: {
    card: 'summary_large_image',
  },
};

export default function AboutPage() {
  return <main>{/* page content */}</main>;
}

Dynamic Pages (Blog Posts, Product Pages)

For routes with dynamic content, use generateMetadata to build the OG image URL from your data:

// app/blog/[slug]/page.tsx

import { getPost } from '@/lib/posts';

export async function generateMetadata({ params }) {
  const post = await getPost(params.slug);
  const ogUrl = new URL('https://ogpeek.com/api/v1/og');
  ogUrl.searchParams.set('title', post.title);
  ogUrl.searchParams.set('subtitle', post.category);
  ogUrl.searchParams.set('template', 'gradient');
  ogUrl.searchParams.set('theme', 'midnight');
  ogUrl.searchParams.set('brandColor', '#FF7A00');

  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      type: 'article',
      images: [
        { url: ogUrl.toString(), width: 1200, height: 630 },
      ],
    },
    twitter: { card: 'summary_large_image' },
  };
}

export default async function BlogPost({ params }) {
  const post = await getPost(params.slug);
  return (
    <article>
      <h1>{post.title}</h1>
      <div>{post.content}</div>
    </article>
  );
}

Using URL and searchParams.set handles encoding automatically—no manual encodeURIComponent calls, no broken ampersands.

Next.js tip: If you are using the Pages Router instead of App Router, set the OG image in <Head> inside getStaticProps or getServerSideProps. The API URL is the same—only the meta tag delivery mechanism differs.

Remix

Remix handles meta tags through its meta export on route modules. Each route can define its own meta tags, including OG images, and Remix merges them with parent routes automatically.

Basic Route with Loader Data

// app/routes/blog.$slug.tsx

import type {
  LoaderFunctionArgs,
  MetaFunction,
} from '@remix-run/node';
import { json } from '@remix-run/node';
import { useLoaderData } from '@remix-run/react';
import { getPost } from '~/lib/posts.server';

export async function loader({ params }: LoaderFunctionArgs) {
  const post = await getPost(params.slug!);
  if (!post) throw new Response('Not Found', { status: 404 });
  return json({ post });
}

export const meta: MetaFunction<typeof loader> = ({ data }) => {
  if (!data) return [{ title: 'Not Found' }];

  const { post } = data;
  const ogImage = `https://ogpeek.com/api/v1/og?title=${
    encodeURIComponent(post.title)
  }&subtitle=${
    encodeURIComponent(post.category)
  }&template=gradient&theme=dark&brandColor=%23FF7A00`;

  return [
    { title: post.title },
    { name: 'description', content: post.excerpt },
    { property: 'og:title', content: post.title },
    { property: 'og:description', content: post.excerpt },
    { property: 'og:type', content: 'article' },
    { property: 'og:image', content: ogImage },
    { property: 'og:image:width', content: '1200' },
    { property: 'og:image:height', content: '630' },
    { name: 'twitter:card', content: 'summary_large_image' },
    { name: 'twitter:image', content: ogImage },
  ];
};

export default function BlogPost() {
  const { post } = useLoaderData<typeof loader>();
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  );
}

Reusable Helper Function

If you have many routes that need OG images, extract a helper to keep your route files clean:

// app/lib/og.ts

export function buildOgImageUrl(
  title: string,
  subtitle?: string,
) {
  const url = new URL('https://ogpeek.com/api/v1/og');
  url.searchParams.set('title', title);
  if (subtitle) url.searchParams.set('subtitle', subtitle);
  url.searchParams.set('template', 'gradient');
  url.searchParams.set('theme', 'midnight');
  url.searchParams.set('brandColor', '#FF7A00');
  return url.toString();
}

// Usage in any route:
// import { buildOgImageUrl } from '~/lib/og';
// const ogImage = buildOgImageUrl(post.title, post.category);

This pattern works well across any Remix project. You define your brand settings once and reuse them everywhere.

Astro

Astro is a content-focused framework that renders HTML at build time by default. Its component-based approach makes OG image integration especially clean because you can create a reusable <BaseHead> component that every page inherits.

Base Layout Component

---
// src/components/BaseHead.astro

interface Props {
  title: string;
  description: string;
  ogSubtitle?: string;
}

const { title, description, ogSubtitle = '' } = Astro.props;
const ogUrl = new URL('https://ogpeek.com/api/v1/og');
ogUrl.searchParams.set('title', title);
if (ogSubtitle) ogUrl.searchParams.set('subtitle', ogSubtitle);
ogUrl.searchParams.set('template', 'gradient');
ogUrl.searchParams.set('theme', 'midnight');
ogUrl.searchParams.set('brandColor', '#FF7A00');
const ogImage = ogUrl.toString();
---

<meta charset="UTF-8" />
<meta name="viewport"
  content="width=device-width, initial-scale=1.0" />
<title>{title}</title>
<meta name="description" content={description} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:image" content={ogImage} />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content={ogImage} />

Using It in a Blog Post Layout

---
// src/layouts/BlogPost.astro

import BaseHead from '../components/BaseHead.astro';

const { frontmatter } = Astro.props;
---

<html lang="en">
  <head>
    <BaseHead
      title={frontmatter.title}
      description={frontmatter.description}
      ogSubtitle={frontmatter.category}
    />
  </head>
  <body>
    <article>
      <h1>{frontmatter.title}</h1>
      <slot />
    </article>
  </body>
</html>

Content Collections

If you are using Astro Content Collections (the recommended approach for blogs), the integration is seamless because each Markdown file already has typed frontmatter:

---
// src/pages/blog/[...slug].astro

import { getCollection } from 'astro:content';
import BlogPost from '../../layouts/BlogPost.astro';

export async function getStaticPaths() {
  const posts = await getCollection('blog');
  return posts.map((post) => ({
    params: { slug: post.slug },
    props: { post },
  }));
}

const { post } = Astro.props;
const { Content } = await post.render();
---

<BlogPost frontmatter={post.data}>
  <Content />
</BlogPost>

Every blog post automatically gets a unique, branded OG image derived from its title and category—no manual image creation required.

Astro tip: Because Astro renders at build time, the OG image URL is baked into the static HTML. Social crawlers fetch the image directly from the OGPeek API at share time. This means your OG images are always live and can be updated by changing API parameters without rebuilding your site.

Advanced Patterns

Shared Configuration Across Frameworks

Across all three frameworks, the same pattern applies: centralize your OG image settings in one place. Define your template, theme, and brand color once, then reference them in every route or component. This keeps your social cards consistent and makes it trivial to rebrand later.

// og-config.ts — works in Next.js, Remix, or any Node project

export const OG_CONFIG = {
  baseUrl: 'https://ogpeek.com/api/v1/og',
  template: 'gradient',
  theme: 'midnight',
  brandColor: '#FF7A00',
} as const;

export function ogImageUrl(
  title: string,
  subtitle?: string,
): string {
  const url = new URL(OG_CONFIG.baseUrl);
  url.searchParams.set('title', title);
  if (subtitle) url.searchParams.set('subtitle', subtitle);
  url.searchParams.set('template', OG_CONFIG.template);
  url.searchParams.set('theme', OG_CONFIG.theme);
  url.searchParams.set('brandColor', OG_CONFIG.brandColor);
  return url.toString();
}

Using the POST Endpoint for Production

The free GET endpoint is great for development and small sites. For production apps with high traffic, the POST endpoint with an API key gives you watermark-free images and higher rate limits. You can call it server-side to pre-warm images or use it in a build script:

// Server-side image generation (any framework)
const response = await fetch('https://ogpeek.com/api/v1/og', {
  method: 'POST',
  headers: {
    'x-api-key': process.env.OGPEEK_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    title: 'My Blog Post',
    subtitle: 'Published on March 28, 2026',
    template: 'gradient',
    theme: 'midnight',
    brandColor: '#FF7A00',
  }),
});

const imageBuffer = await response.arrayBuffer();
// Save to disk, upload to CDN, etc.

Pricing for Framework Developers

OGPeek is built for developers who want to ship fast and not think about image infrastructure. Here is how pricing maps to typical use cases:

Most developer blogs fit comfortably within the free tier. You only need a paid plan when you are generating thousands of unique images per month or need watermark-free output.

Common Pitfalls

A few things to watch out for when integrating OG images into any framework:

Framework Comparison at a Glance

Here is a quick summary of where you set OG meta tags in each framework:

In every case, the OGPeek API URL is identical. The only thing that changes is how your framework delivers that URL to the <meta> tag in the HTML output.

Start generating OG images now

50 free images per day. No signup required. Works with Next.js, Remix, Astro, and every other framework.

Open the playground →

Wrapping Up

Dynamic OG image generation should not require a different approach for every framework. The pattern is the same everywhere: construct a URL with your title, template, and brand settings, then set it as the og:image meta tag using whatever mechanism your framework provides.

Next.js gives you generateMetadata. Remix gives you the meta export. Astro gives you component props and frontmatter. All three work with a single OGPeek API URL.

If you are building a SaaS or a content-heavy site in any of these frameworks, automating your social preview images with an API is one of the highest-leverage changes you can make for click-through rates. Set it up once, and every new page automatically gets a branded, professional social card.

For more on OG images, check out our complete API guide, our image size reference, the static site generator integration guide, or our guide to automating OG images for SaaS blogs. And explore the full Peek Suite for more developer APIs including tech stack detection, SEO audits, and cron monitoring.

More developer APIs from the Peek Suite