← All articles · April 27, 2026 · og.hjlabs.in

OG Images in Next.js, Astro, SvelteKit 2026 — Copy-Paste Code

Every modern framework has its own pattern for OG images. Here are the production-grade recipes for the five most common stacks: Next.js, Astro, SvelteKit, Nuxt, Remix.

Next.js (App Router)

Two approaches: static opengraph-image file or dynamic route handler.

Static OG image per page

Drop a file at app/blog/[slug]/opengraph-image.tsx:

import { ImageResponse } from 'next/og';
export const alt = 'Blog post';
export const size = { width: 1200, height: 630 };
export default async function Image({ params }) {
  const post = await getPost(params.slug);
  return new ImageResponse(
    <div style={{ display: 'flex', fontSize: 60 }}>{post.title}</div>,
    size
  );
}

Use og.hjlabs.in (no code)

If you don't want to ship Satori in your edge bundle, just use a URL:

export async function generateMetadata({ params }) {
  const post = await getPost(params.slug);
  return {
    openGraph: {
      images: [`https://og.hjlabs.in/api/og?title=${encodeURIComponent(post.title)}&template=blog`]
    }
  };
}

Astro

Astro doesn't have a built-in OG image API, but two patterns work well:

Use og.hjlabs.in URL

--- Layout.astro ---
const { title, description } = Astro.props;
const ogImage = `https://og.hjlabs.in/api/og?title=${encodeURIComponent(title)}&template=blog`;
---
<meta property="og:image" content={ogImage} />

Self-hosted with Satori

Use the satori-html package in an Astro endpoint at src/pages/og/[slug].png.ts. More work but fully self-hosted.

SvelteKit

SvelteKit endpoints can return any content type. Pattern:

// src/routes/og/[slug]/+server.ts
import satori from 'satori';
import { Resvg } from '@resvg/resvg-js';

export const GET = async ({ params }) => {
  const svg = await satori(/* JSX */, { width: 1200, height: 630, fonts: [] });
  const png = new Resvg(svg).render().asPng();
  return new Response(png, { headers: { 'Content-Type': 'image/png' } });
};

Or just use og.hjlabs.in URL in your +layout.svelte.

Nuxt 3

Nuxt has the nuxt-og-image module which is excellent. Install:

npm i nuxt-og-image
// nuxt.config.ts
export default defineNuxtConfig({ modules: ['nuxt-og-image'] });

Then in any page:

<script setup>
defineOgImageComponent('BlogPost', { title: post.title });
</script>

Or fall back to the useSeoMeta + og.hjlabs.in URL pattern.

Remix

Remix doesn't have an official OG library yet (as of 2026). Use a resource route:

// app/routes/og.$slug[.png].ts
export const loader = async ({ params }) => {
  // Use satori or fetch from og.hjlabs.in
  const res = await fetch(`https://og.hjlabs.in/api/og?title=${encodeURIComponent(params.slug)}`);
  return new Response(res.body, { headers: { 'Content-Type': 'image/png' } });
};

Plain HTML / Static sites (Hugo, Jekyll, 11ty)

Use the og.hjlabs.in URL directly in your front-matter or template:

{% if page.title %}
<meta property="og:image" content="https://og.hjlabs.in/api/og?title={{ page.title | url_encode }}&template=blog">
{% endif %}

Common framework gotchas

  1. Next.js Edge runtime: some Node-only image libraries break. Stick to ImageResponse or pure URL.
  2. Astro static build: if you build statically, OG image generation must happen at build time, not request time. Use the build-time hook.
  3. SvelteKit prerender: mark your OG endpoint as prerender = true if URLs are knowable at build.
  4. Nuxt SSR: ensure nuxt-og-image matches your renderer (server vs static).

Testing your setup

  1. Build and deploy your site
  2. Open the OG image URL directly in a browser — it should render
  3. Use Twitter Card Validator and Facebook Debugger
  4. Share the page link in Slack/Discord — these tools also fetch OG images

Bottom line

Pick the framework-native solution if you need full control. Pick a hosted service like og.hjlabs.in if you want to ship in 5 minutes. The framework-native path costs you 4-8 hours of setup and maintenance per project — for most blogs, the hosted route is the better tradeoff.

Generate your OG images and favicons

Free. URL-based API. Edge-cached. No signup.

Open the generator →

More from the blog