March 29, 2026 · 8 min read

How to Add Dynamic OG Images to Gatsby, Remix, and SvelteKit (2026)

Gatsby, Remix, and SvelteKit each have a fundamentally different model for injecting <meta> tags — and that difference makes setting up OG images unnecessarily painful in all three. This guide covers the canonical approach for each framework and shows how to wire in a single API that works across all of them.

The Problem: No Unified Standard

Open Graph meta tags belong in <head>. Simple enough. But how you get there varies dramatically by framework:

None of these approaches are wrong. But if you work across all three — or if your team is evaluating frameworks — you'll hit friction with each one. And that's before the harder problem: the OG image itself.

Manually creating a 1200×630 PNG for every page doesn't scale. You need dynamic images that reflect the actual page title and branding. That's where an API-based approach pays off: one URL pattern works everywhere, regardless of framework.

The Universal Solution: OGPeek API

The OGPeek API generates Open Graph images from URL parameters. You pass a title, subtitle, template, and theme — it returns a ready-to-use PNG. No headless browser, no build step, no manual exports.

The base URL structure looks like this:

https://ogpeek.com/api/v1/og
  ?title=YOUR_PAGE_TITLE
  &subtitle=YOUR_SITE_NAME
  &template=gradient
  &theme=midnight
  &brandColor=%23FF7A00

Every framework example below uses this same URL pattern — only the syntax for injecting it into <head> changes.

Example OG image generated by OGPeek

Gatsby OG Images

Gatsby

Prerequisites

Install gatsby-plugin-react-helmet and react-helmet if you haven't already:

npm install gatsby-plugin-react-helmet react-helmet

Add the plugin to gatsby-config.js:

// gatsby-config.js
module.exports = {
  plugins: [
    'gatsby-plugin-react-helmet',
    // ...other plugins
  ],
};

Page-Level OG Tags with React Helmet

In any Gatsby page component, import Helmet and add your meta tags. The OGPeek URL is built dynamically from the page's title:

// src/pages/blog/{post.slug}.js
import React from 'react';
import { Helmet } from 'react-helmet';
import { graphql } from 'gatsby';

export default function BlogPost({ data }) {
  const post = data.markdownRemark;
  const title = post.frontmatter.title;
  const siteUrl = 'https://yoursite.com';

  // Build OGPeek URL — title comes from your CMS/GraphQL, not user input
  const ogImageUrl =
    'https://ogpeek.com/api/v1/og' +
    '?title=' + encodeURIComponent(title) +
    '&subtitle=Your+Blog+Name' +
    '&template=gradient' +
    '&theme=midnight' +
    '&brandColor=%23FF7A00';

  return (
    <>
      <Helmet>
        <title>{title}</title>
        <meta property="og:title" content={title} />
        <meta property="og:type" content="article" />
        <meta
          property="og:url"
          content={siteUrl + '/blog/' + post.fields.slug}
        />
        <meta property="og:image" content={ogImageUrl} />
        <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:title" content={title} />
        <meta name="twitter:image" content={ogImageUrl} />
      </Helmet>
      <main>
        <h1>{title}</h1>
        <div>{post.excerpt}</div>
      </main>
    </>
  );
}

export const query = graphql`
  query($id: String!) {
    markdownRemark(id: { eq: $id }) {
      excerpt
      fields { slug }
      frontmatter { title }
    }
  }
`;

SSR Verification via gatsby-ssr.js

Gatsby's gatsby-ssr.js file runs during gatsby build and lets you wrap every page's HTML output. If you're building a reusable SEO component, this is where to add fallback tags:

// gatsby-ssr.js
const React = require('react');

exports.onRenderBody = ({ setHeadComponents }) => {
  setHeadComponents([
    React.createElement('meta', {
      key: 'og-image-default',
      property: 'og:image',
      content:
        'https://ogpeek.com/api/v1/og' +
        '?title=Your+Site' +
        '&template=basic' +
        '&theme=midnight',
    }),
  ]);
};

Note: Page-level <Helmet> tags override any defaults set in gatsby-ssr.js. Use gatsby-ssr.js for site-wide fallbacks only.

For a deeper walkthrough of Gatsby-specific options, see the full Gatsby OG images guide.

Remix OG Images

Remix

How the meta Export Works

Remix takes a radically different approach: each route file exports a meta function that returns an array of meta tag descriptors. No JSX, no component — just data. The runtime merges all ancestor meta exports and renders them into <head>.

Basic Route Meta Export

// app/routes/blog.$slug.tsx
import type { MetaFunction, LoaderFunctionArgs } from '@remix-run/node';
import { json } from '@remix-run/node';
import { useLoaderData } from '@remix-run/react';

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: 'Not Found' }];

  const { post } = data;
  const ogImageUrl =
    'https://ogpeek.com/api/v1/og' +
    '?title=' + encodeURIComponent(post.title) +
    '&subtitle=Your+Blog+Name' +
    '&template=gradient' +
    '&theme=midnight' +
    '&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:url', content: 'https://yoursite.com/blog/' + post.slug },
    { 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>
      <p>{post.excerpt}</p>
    </article>
  );
}

Root-Level Fallback Meta

In Remix, app/root.tsx typically defines site-wide default meta. Provide a fallback OG image there so any route without an explicit meta export still gets a valid social card:

// app/root.tsx (excerpt)
export const meta: MetaFunction = () => [
  { title: 'Your Site' },
  {
    property: 'og:image',
    content:
      'https://ogpeek.com/api/v1/og' +
      '?title=Your+Site' +
      '&template=basic' +
      '&theme=midnight',
  },
  { name: 'twitter:card', content: 'summary_large_image' },
];

Remix v2 note: The meta export receives matches as a second argument, giving you access to ancestor route data. Use it to inherit your brand name or accent color from the root loader rather than hard-coding it in every route.

See the dedicated Remix OG images guide for advanced patterns including resource routes and social share cards for user-generated content.

SvelteKit OG Images

SvelteKit

How SvelteKit Handles Head Tags

SvelteKit uses a two-step pattern. First, a +page.server.ts (or +page.ts) load function fetches data and returns it. Second, the +page.svelte component uses a <svelte:head> block to render meta tags reactively from that data.

Step 1 — Load Function

// src/routes/blog/[slug]/+page.server.ts
import type { PageServerLoad } from './$types';
import { error } from '@sveltejs/kit';

export const load: PageServerLoad = async ({ params }) => {
  const post = await getPostBySlug(params.slug);
  if (!post) throw error(404, 'Not found');

  const ogImageUrl =
    'https://ogpeek.com/api/v1/og' +
    '?title=' + encodeURIComponent(post.title) +
    '&subtitle=Your+Blog+Name' +
    '&template=gradient' +
    '&theme=midnight' +
    '&brandColor=%23FF7A00';

  return { post, ogImageUrl };
};

Step 2 — svelte:head in the Page Component

<!-- src/routes/blog/[slug]/+page.svelte -->
<script lang="ts">
  import type { PageData } from './$types';
  export let data: PageData;
  const { post, ogImageUrl } = data;
</script>

<svelte:head>
  <title>{post.title}</title>
  <meta name="description" content={post.excerpt} />
  <meta property="og:title" content={post.title} />
  <meta property="og:description" content={post.excerpt} />
  <meta property="og:type" content="article" />
  <meta property="og:url" content="https://yoursite.com/blog/{post.slug}" />
  <meta property="og:image" content={ogImageUrl} />
  <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:title" content={post.title} />
  <meta name="twitter:image" content={ogImageUrl} />
</svelte:head>

<article>
  <h1>{post.title}</h1>
  <p>{post.excerpt}</p>
</article>

Site-Wide Fallback in +layout.svelte

For pages that don't define their own OG tags, add a default in your root layout:

<!-- src/routes/+layout.svelte -->
<svelte:head>
  <meta
    property="og:image"
    content="https://ogpeek.com/api/v1/og?title=Your+Site&template=basic&theme=midnight"
  />
  <meta name="twitter:card" content="summary_large_image" />
</svelte:head>

SSR vs. CSR: Build the OGPeek URL in +page.server.ts (not in the component script) so the meta tags are present in the HTML sent to crawlers. Social platform bots do not execute JavaScript.

For the complete SvelteKit walkthrough including adapter-specific considerations, see the SvelteKit OG images guide.

Testing Your OG Images

After deploying, verify that crawlers see the correct tags before you share any links. Two tools cover the major platforms:

Twitter Card Validator

Go to cards-dev.twitter.com/validator, paste your URL, and click Preview card. This shows exactly what Twitter will render when someone tweets your link. Look for:

Facebook Sharing Debugger

Go to developers.facebook.com/tools/debug and enter your URL. If you've recently updated your meta tags, click Scrape Again to clear Facebook's cache. Confirm:

Quick curl Check

Before reaching for a browser tool, a quick curl confirms the tags are present in the server-rendered HTML:

curl -s https://yoursite.com/blog/your-post-slug \
  | grep -i "og:image"

If the output is empty, the meta tags are being injected client-side only — move the OGPeek URL construction to your server-side load function or SSR hook.

API Parameters Reference

All three frameworks use the same OGPeek URL. Here's a quick reference of the parameters you'll use most often:

Full parameter documentation is on the OGPeek API Docs page. For plan details, see the pricing page.

Start generating OG images now

No signup required for the free tier. Upgrade to remove the watermark and unlock higher request limits.

Open the playground →

Conclusion

Gatsby, Remix, and SvelteKit each have their own mental model for head tag injection — but once you understand each framework's pattern, the integration is straightforward. The code above is the canonical approach for each:

In every case, the OGPeek API reduces the image problem to a single URL. No design tools, no build-time image generation, no extra dependencies per framework. The same API call works in all three — and in any other framework you might adopt next.

Related Guides

Looking for a deeper dive into a specific framework, or want to explore other static site generators?

More developer APIs from the Peek Suite