Svelte 5 Open Graph Meta Tags: Runes, SSR & OG Images
How to add og:title, og:image, og:description in Svelte 5 using the new runes API, SvelteKit head management, and server-side rendering for social crawlers.
What changed in Svelte 5 for OG tags
Svelte 5 introduced runes — a new reactivity system that replaces stores and reactive declarations. While OG tags themselves haven't changed, how you manage head metadata in SvelteKit with Svelte 5 components has some new patterns worth understanding.
The good news: SvelteKit still handles SSR perfectly, which is critical for OG tags. Social crawlers from Twitter, LinkedIn, Discord, Slack, and Facebook don't execute JavaScript — they only read the initial HTML response. SvelteKit renders your<svelte:head> content server-side, so crawlers see your OG tags immediately.
Static OG tags with svelte:head
For pages with fixed metadata, use <svelte:head> directly in your page component:
<!-- src/routes/+page.svelte (Svelte 5) --> <svelte:head> <title>My SaaS — Ship faster with AI</title> <meta property="og:title" content="My SaaS — Ship faster with AI" /> <meta property="og:description" content="Deploy to production in 30 seconds." /> <meta property="og:image" content="https://mysaas.com/og/home.png" /> <meta property="og:image:width" content="1200" /> <meta property="og:image:height" content="630" /> <meta property="og:type" content="website" /> <meta property="og:url" content="https://mysaas.com" /> <meta name="twitter:card" content="summary_large_image" /> </svelte:head> <h1>Welcome to My SaaS</h1>
Dynamic OG tags with load functions
For dynamic pages like blog posts or user profiles, fetch data in a +page.server.ts load function and use it in your <svelte:head>:
// src/routes/blog/[slug]/+page.server.ts
import type { PageServerLoad } from './$types';
import { getPost } from '$lib/posts';
export const load: PageServerLoad = async ({ params }) => {
const post = await getPost(params.slug);
return {
title: post.title,
description: post.excerpt,
image: post.ogImage || `https://mysaas.com/og/blog/${params.slug}.png`,
slug: params.slug,
};
};<!-- src/routes/blog/[slug]/+page.svelte (Svelte 5 runes) -->
<script>
let { data } = $props();
</script>
<svelte:head>
<title>{data.title} — My SaaS Blog</title>
<meta property="og:title" content={data.title} />
<meta property="og:description" content={data.description} />
<meta property="og:image" content={data.image} />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:type" content="article" />
<meta property="og:url" content={`https://mysaas.com/blog/${data.slug}`} />
<meta name="twitter:card" content="summary_large_image" />
</svelte:head>
<article>
<h1>{data.title}</h1>
</article>With Svelte 5's $props() rune, you destructure the data directly instead of using the old export let data syntax. The <svelte:head> behavior is identical.
OG images with SvelteKit endpoints
Generate dynamic OG images using a SvelteKit server endpoint with Satori or @resvg/resvg-js:
// src/routes/og/[slug].png/+server.ts
import satori from 'satori';
import { Resvg } from '@resvg/resvg-js';
import type { RequestHandler } from './$types';
export const GET: RequestHandler = async ({ params }) => {
const post = await getPost(params.slug);
const svg = await satori(
{
type: 'div',
props: {
style: {
display: 'flex',
flexDirection: 'column',
background: '#0a0a0a',
width: '100%',
height: '100%',
padding: '60px',
},
children: [
{
type: 'div',
props: {
style: { fontSize: 64, fontWeight: 900, color: '#fff' },
children: post.title,
},
},
],
},
},
{ width: 1200, height: 630, fonts: [/* load your font */] }
);
const png = new Resvg(svg).render().asPng();
return new Response(png, {
headers: { 'Content-Type': 'image/png', 'Cache-Control': 'public, max-age=86400' },
});
};Testing OG tags in Svelte 5
After adding OG tags, verify they work:
- Run
npm run build && npm run previewand view page source — OG meta tags should be in the HTML - Deploy to your hosting provider and test the live URL with OGFixer
- Check Twitter Card Validator and Facebook Sharing Debugger
- Share the link in a private Slack/Discord channel to see the actual preview
Common Svelte 5 OG pitfalls
- Forgetting to use a
+page.server.tsload function — client-side fetches won't render in SSR HTML - Using
$state()for OG data — runes are client-side reactive state, not SSR data; use load functions instead - Putting OG images behind authentication — social crawlers can't authenticate
- Using relative URLs for
og:image— always use absolute URLs with https://
Test your Svelte 5 app's OG tags
Paste your deployed SvelteKit URL to see how it looks on Twitter, Slack, Discord, and LinkedIn — with specific fixes for any issues.
Check your Svelte 5 OG tags →