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 constalt ='Blog post';export constsize = { width:1200, height:630};export default async functionImage({ params }) {constpost =awaitgetPost(params.slug);return newImageResponse( <divstyle={{ 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 functiongenerateMetadata({ params }) {constpost =awaitgetPost(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;constogImage =`https://og.hjlabs.in/api/og?title=${encodeURIComponent(title)}&template=blog`;---<metaproperty="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.tsimportsatorifrom'satori';import{ Resvg }from'@resvg/resvg-js';export constGET=async({ params }) => {constsvg =awaitsatori(/* JSX */, { width:1200, height:630, fonts: [] });constpng =newResvg(svg).render().asPng();return newResponse(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.tsexport defaultdefineNuxtConfig({ 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].tsexport constloader=async({ params }) => {// Use satori or fetch from og.hjlabs.inconstres =awaitfetch(`https://og.hjlabs.in/api/og?title=${encodeURIComponent(params.slug)}`);return newResponse(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
- Next.js Edge runtime: some Node-only image libraries break. Stick to ImageResponse or pure URL.
- Astro static build: if you build statically, OG image generation must happen at build time, not request time. Use the build-time hook.
- SvelteKit prerender: mark your OG endpoint as
prerender = trueif URLs are knowable at build. - Nuxt SSR: ensure
nuxt-og-imagematches your renderer (server vs static).
Testing your setup
- Build and deploy your site
- Open the OG image URL directly in a browser — it should render
- Use Twitter Card Validator and Facebook Debugger
- 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 →