March 29, 2026 · 9 min read

How to Add Dynamic OG Images to Remix with OGPeek API

Remix gives you nested routes, loaders, and a powerful meta export for controlling every page's metadata. But generating unique Open Graph images per route is still tedious—until now. Here's how to add dynamic, branded social cards to every Remix route in under five minutes using a single API URL.

Dynamic OG image for Remix apps generated by OGPeek API

Why Remix Apps Need Dynamic OG Images

Every time someone shares a link to your Remix app on Twitter/X, LinkedIn, Slack, or Discord, the platform's crawler fetches your page and looks for the og:image meta tag. That image is the first thing people see before they decide whether to click.

If every route on your app shares the same generic image—or worse, has no image at all—your links blend into the feed. Pages with unique, relevant OG images get significantly more engagement than those with generic previews.

Remix's architecture makes this both easier and harder than you might expect:

The simplest approach is to skip the rendering pipeline entirely and point your og:image tag at an API that returns a PNG. That is what OGPeek does.

How OGPeek Works

OGPeek is an OG image generation API. You construct a URL with your title, subtitle, template, and brand color as query parameters. When that URL is requested, OGPeek renders the image and returns a 1200x630 PNG—the standard size for Open Graph images across all major platforms.

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

That is the entire integration. A GET request returns a PNG. No API keys for the free tier, no SDK to install, no resource routes to create. You embed this URL in your Remix route's meta export and every page gets a unique social card based on its title.

Key point: Social platform crawlers follow the URL in your og:image meta tag and fetch whatever is there. They cannot tell the difference between a static PNG on your CDN and an API endpoint that generates a PNG on the fly. Both are just HTTP responses with Content-Type: image/png.

Step 1: Add OGPeek to a Remix Route's Meta Export

Remix uses a meta export function on each route to define the page's meta tags. This is where the OG image integration goes. Here is a basic example for a static route:

// app/routes/about.tsx
import type { MetaFunction } from "@remix-run/node";

export const meta: MetaFunction = () => {
  const title = "About Us";
  const description = "Learn about our team and mission.";

  const ogImageUrl = `https://ogpeek.com/api/v1/og?title=${
    encodeURIComponent(title)
  }&subtitle=${
    encodeURIComponent("yourdomain.com")
  }&template=gradient&theme=midnight&brandColor=%23FF7A00`;

  return [
    { title },
    { name: "description", content: description },
    { property: "og:title", content: title },
    { property: "og:description", content: description },
    { property: "og:image", content: ogImageUrl },
    { property: "og:image:width", content: "1200" },
    { property: "og:image:height", content: "630" },
    { name: "twitter:card", content: "summary_large_image" },
    { name: "twitter:title", content: title },
    { name: "twitter:description", content: description },
    { name: "twitter:image", content: ogImageUrl },
  ];
};

That is it for a static route. The meta function returns an array of meta tag descriptors, and Remix renders them into the <head> of your page. No additional packages, no build configuration, no resource routes.

Step 2: Dynamic OG Images from Loader Data

The real power of Remix is its loaders—server-side functions that fetch data before the route renders. The meta function receives the loader data as an argument, which means you can build OGPeek URLs from any data your loader returns.

Here is a blog post route that generates a unique OG image based on the post title from a database or CMS:

// 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 { getPostBySlug } from "~/models/post.server";

export async function loader({ params }: LoaderFunctionArgs) {
  const post = await getPostBySlug(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: "Post Not Found" }];

  const { post } = data;

  const ogImageUrl = `https://ogpeek.com/api/v1/og?title=${
    encodeURIComponent(post.title)
  }&subtitle=${
    encodeURIComponent(post.category + " · yourdomain.com")
  }&template=gradient&theme=midnight&brandColor=%23FF7A00`;

  return [
    { title: post.title + " — Your Blog" },
    { name: "description", content: post.excerpt },
    { property: "og:title", content: post.title },
    { property: "og:description", content: post.excerpt },
    { property: "og:image", content: ogImageUrl },
    { property: "og:image:width", content: "1200" },
    { property: "og:image:height", content: "630" },
    { name: "twitter:card", content: "summary_large_image" },
    { name: "twitter:title", content: post.title },
    { name: "twitter:image", content: ogImageUrl },
  ];
};

export default function BlogPost() {
  const { post } = useLoaderData<typeof loader>();

  return (
    <article>
      <h1>{post.title}</h1>
      <div>{post.content}</div>
    </article>
  );
}

Every blog post now gets a branded OG image with its own title and category. A post titled "Getting Started with Remix" in the "Tutorials" category produces a social card with that exact text—no manual image creation needed.

Helper Function for Cleaner Code

If you have many routes, extract the OGPeek URL construction into a utility function:

// app/utils/og.ts
export function buildOgImageUrl(title: string, subtitle?: string): string {
  const params = new URLSearchParams({
    title,
    subtitle: subtitle ?? "yourdomain.com",
    template: "gradient",
    theme: "midnight",
    brandColor: "#FF7A00",
  });
  return `https://ogpeek.com/api/v1/og?${params.toString()}`;
}

Then use it in any route's meta export:

import { buildOgImageUrl } from "~/utils/og";

export const meta: MetaFunction<typeof loader> = ({ data }) => {
  const ogImage = buildOgImageUrl(data.post.title, data.post.category);
  return [
    { property: "og:image", content: ogImage },
    // ... other meta tags
  ];
};

Step 3: Parent Route Meta and Nested Routes

Remix's nested routing means child routes can define their own meta, overriding or extending the parent. You can set a default OG image in your root route and override it per child route:

// app/root.tsx — default OG image for the entire app
export const meta: MetaFunction = () => {
  const ogImage = `https://ogpeek.com/api/v1/og?title=${
    encodeURIComponent("Your App Name")
  }&subtitle=yourdomain.com&template=gradient&theme=midnight&brandColor=%23FF7A00`;

  return [
    { title: "Your App Name" },
    { property: "og:image", content: ogImage },
    { name: "twitter:card", content: "summary_large_image" },
    { name: "twitter:image", content: ogImage },
  ];
};

// app/routes/pricing.tsx — route-specific override
export const meta: MetaFunction = () => {
  const ogImage = `https://ogpeek.com/api/v1/og?title=${
    encodeURIComponent("Simple, Transparent Pricing")
  }&subtitle=yourdomain.com&template=gradient&theme=midnight&brandColor=%23FF7A00`;

  return [
    { title: "Pricing — Your App Name" },
    { property: "og:image", content: ogImage },
    { name: "twitter:card", content: "summary_large_image" },
    { name: "twitter:image", content: ogImage },
  ];
};

Every route that defines its own og:image in meta will use that image. Routes that do not define one will fall back to the root route's default. This gives you full control with minimal duplication.

Customizing Your OG Images

OGPeek supports several parameters to match your brand:

You can also make the subtitle dynamic. For example, pass the blog category or author name:

const ogImageUrl = buildOgImageUrl(
  post.title,
  `${post.author} · ${post.category}`
);

OGPeek vs @vercel/og in Remix Projects

The most common alternative for dynamic OG images in Remix is the @vercel/og library combined with a resource route. Here is how the two approaches compare:

Feature OGPeek API @vercel/og
Setup time 2 minutes 20–40 minutes
Requires resource route No Yes (renders JSX to image)
Hosting lock-in None Best on Vercel (edge runtime)
Dependencies added 0 @vercel/og + Satori
CSS support in templates Pre-built templates Subset (Flexbox only, no Grid)
Works on Fly.io / Railway Yes Requires workarounds
Build time impact Zero Zero (runtime)
Scales to 1000+ routes Yes (CDN cached) Yes (per-request render)
Custom JSX layouts No Full control

For most Remix apps—blogs, SaaS landing pages, documentation sites, e-commerce storefronts—OGPeek's pre-built templates cover the common use cases without requiring you to write and maintain image rendering code. If you need pixel-perfect custom layouts with arbitrary JSX, @vercel/og gives you more control at the cost of hosting constraints and additional complexity.

Tip: If you deploy your Remix app to Fly.io, Railway, AWS, or Cloudflare Workers, @vercel/og may require workarounds or alternative setups. OGPeek works identically on every hosting provider because the image generation happens on OGPeek's servers, not yours.

Testing Your OG Images

After deploying your Remix app, verify your OG images work correctly:

  1. View the page source and confirm the og:image meta tag contains a valid OGPeek URL with the correct page title.
  2. Open the OGPeek URL directly in your browser. You should see a 1200x630 PNG with your title rendered on it.
  3. Use a debugger tool like the Twitter Card Validator or Facebook Sharing Debugger to see exactly what the platform will render.
  4. Share a link in a private Slack channel or Discord server to see the preview in context.

If the image does not appear, check that your encodeURIComponent() call is encoding special characters in your title correctly. Ampersands, quotes, and hash symbols in titles can break the URL if left unencoded.

Performance Considerations

OGPeek caches generated images at the CDN edge. The first request for a given URL renders the image and caches it. Subsequent requests—including from different social platforms sharing the same link—are served from the cache with sub-50ms response times.

Since the og:image URL is only fetched by social platform crawlers (not by your app's visitors), there is zero impact on your Remix app's performance. Core Web Vitals, Lighthouse scores, and page load times are completely unaffected. Your loaders and route rendering stay fast.

Frequently Asked Questions

Does OGPeek work with Remix deployed on Fly.io, Railway, or AWS?

Yes. OGPeek works with any hosting provider. Since the OG image is generated by OGPeek's API servers, your Remix app just needs to output the correct og:image meta tag. It does not matter where your app is deployed.

Do I need to set up a resource route for OG images?

No. With OGPeek, you do not need a resource route or any server-side image generation. You simply include the OGPeek API URL in your meta export function. The image is generated externally when a social platform crawler requests it.

How is OGPeek different from @vercel/og in a Remix project?

@vercel/og requires you to set up a resource route that runs Satori to render JSX into an image, and it works best on Vercel's edge runtime. OGPeek is a hosted API—you construct a URL with query parameters and receive a PNG. No resource routes, no Satori, no edge runtime, no hosting lock-in.

Can I pass dynamic loader data into OGPeek URLs?

Yes. Remix's meta function receives the loader data as an argument. You can use the title, description, category, or any other field from your loader response to construct the OGPeek URL dynamically. Each route gets a unique OG image based on its data.

What image dimensions does OGPeek return?

OGPeek returns 1200x630 PNG images by default. This is the recommended size for Open Graph images across Facebook, LinkedIn, Twitter/X, Slack, Discord, and all other platforms that render link previews.

Start generating OG images for your Remix app

Read the full API documentation, explore templates, and grab your integration URL. Free tier available—no signup required.

Get started free → Read the API docs

Conclusion

Remix's meta export and loader architecture make it straightforward to add per-route metadata. Adding dynamic OG images should be just as simple. With OGPeek, you construct a URL in your meta function, pass the page title from your loader data, and every route on your app gets a unique, branded social preview card.

No resource routes. No Satori. No edge runtime dependencies. No hosting lock-in. Just a GET request that returns a PNG.

Your Remix app stays fast. Your social cards stay unique.

More developer APIs from the Peek Suite

← Back to all blog posts