SvelteKit Open Graph Meta Tags: Use +page.svelte and load() for SSR OG

Add og:title, og:image, og:description, and Twitter card tags to SvelteKit apps using svelte:head, +layout.svelte, and server-side load() functions.

SvelteKit and OG tags

SvelteKit renders pages on the server by default, which is ideal for social media previews. Social crawlers receive fully populated HTML with OG tags already in the response. You set meta tags using Svelte's built-in <svelte:head> element, and for dynamic content, you load data server-side in +page.server.ts before rendering.

Note: if you're using the plain Svelte adapter (SPA mode), crawlers won't execute JavaScript. Make sure your SvelteKit site uses SSR (the default) or SSG.

Adding OG tags with svelte:head

Use <svelte:head> inside any +page.svelte to inject meta tags:

<!-- src/routes/+page.svelte -->
<svelte:head>
  <title>My SvelteKit Site</title>
  <meta name="description" content="My site description." />
  <meta property="og:title" content="My SvelteKit Site" />
  <meta property="og:description" content="My site description." />
  <meta property="og:image" content="https://example.com/og.jpg" />
  <meta property="og:url" content="https://example.com" />
  <meta property="og:type" content="website" />
  <meta name="twitter:card" content="summary_large_image" />
</svelte:head>

<h1>Welcome</h1>

Dynamic OG tags from server load()

For dynamic pages, load data in +page.server.ts and pass it to the page:

// 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 getPost(params.slug)
  if (!post) throw error(404, 'Post not found')
  return { post }
}

<!-- src/routes/blog/[slug]/+page.svelte -->
<script>
  export let data
  const { post } = 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:image" content={post.coverImage} />
  <meta property="og:url" content={`https://example.com/blog/${post.slug}`} />
  <meta property="og:type" content="article" />
  <meta name="twitter:card" content="summary_large_image" />
</svelte:head>

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

Default OG tags in +layout.svelte

Set default OG tags in your root layout. Child pages can override individual tags:

<!-- src/routes/+layout.svelte -->
<svelte:head>
  <meta property="og:type" content="website" />
  <meta name="twitter:card" content="summary_large_image" />
  <meta property="og:image" content="https://example.com/og-default.jpg" />
</svelte:head>

<slot />

Note: child <svelte:head> tags are merged with parent layout tags. Duplicate property names are resolved by the last value in document order, so child tags will override layout defaults.

Common SvelteKit OG mistakes

  • SPA adapter: using the @sveltejs/adapter-static with fallback: 'index.html' creates a SPA with no per-page HTML. Every page shares the same HTML — OG tags will be identical. Use prerender = true per route instead.
  • Reactive updates not needed: don't make OG meta reactive with $:. The values only matter for the initial server render.
  • Relative image URLs: og:image must be an absolute HTTPS URL.

Preview your OG tags free at OGFixer.com → Paste your SvelteKit URL to see how your link card looks on Twitter, LinkedIn, Discord, and Slack.

← All guides