How to Create Dynamic Social Cards in a Svelte App
Create route-specific social previews in SvelteKit with server-rendered Open Graph tags and generated images. Choose between prerendered cards and on-demand image routes.
To create dynamic social cards in a SvelteKit app, load route-specific content on the server, render its Open Graph metadata in <svelte:head>, and point og:image at a publicly reachable image. For cards whose artwork changes with the route, serve an image from a SvelteKit +server.ts endpoint. Prerender the endpoint when the route set and content are known at build time; generate images on demand when content changes at runtime.
This guide uses SvelteKit. A plain Svelte client-rendered app needs a server or static build step that can deliver the metadata in the initial HTML; changing the DOM after hydration is not enough for crawlers that read the original response.
1. How social card metadata works
Open Graph defines four basic properties: og:title, og:type, og:image, and og:url. Add og:description for a useful summary and og:image:alt to describe the image. Each route should emit metadata for that route, including its own canonical URL.
SvelteKit normally renders pages on the server or prerenders them, then sends HTML to the browser. That lets a crawler receive page-specific head tags without running client-side JavaScript. The framework inserts <svelte:head> output into the document head.
Social platforms have their own image requirements and refresh behavior. The sources used for this guide do not establish universal dimensions, format limits, or cache rules, so check the current documentation for the platform you target and validate the deployed URL.
2. Load route data and render Open Graph tags
Put the data lookup in a server-capable load function so the title, description, canonical URL, and image URL are available when SvelteKit renders the page. For example, a blog post route can use src/routes/posts/[slug]/+page.server.ts and src/routes/posts/[slug]/+page.svelte.
// src/routes/posts/[slug]/+page.server.ts
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
const posts = {
'dynamic-cards': {
title: 'Dynamic social cards in SvelteKit',
description: 'Build route-specific Open Graph metadata and images.',
image: 'https://example.com/api/og/dynamic-cards.png'
}
};
export const load: PageServerLoad = ({ params, url }) => {
const post = posts[params.slug as keyof typeof posts];
if (!post) error(404, 'Post not found');
return {
...post,
canonicalUrl: new URL(`/posts/${params.slug}`, url.origin).href
};
};
<!-- src/routes/posts/[slug]/+page.svelte -->
<script lang="ts">
import type { PageData } from './$types';
let { data }: { data: PageData } = $props();
</script>
<svelte:head>
<title>{data.title}</title>
<meta name="description" content={data.description} />
<link rel="canonical" href={data.canonicalUrl} />
<meta property="og:title" content={data.title} />
<meta property="og:type" content="article" />
<meta property="og:description" content={data.description} />
<meta property="og:url" content={data.canonicalUrl} />
<meta property="og:image" content={data.image} />
<meta property="og:image:alt" content={`Illustration for ${data.title}`} />
</svelte:head>
<article>
<h1>{data.title}</h1>
<p>{data.description}</p>
</article>
The sample uses a small in-memory map so the example is runnable as a route once placed in a SvelteKit project. Replace it with your database or CMS lookup as appropriate. Use the request origin only if your deployment’s host handling is configured and trusted; otherwise construct canonical URLs from a configured site origin. Do not interpolate untrusted strings into hand-built HTML. Svelte expressions in head attributes are escaped by the framework.
Open Graph property names and requirements are specified by the Open Graph protocol. SvelteKit’s behavior and page options are described in its page options documentation and load documentation.
3. Generate a card image on a SvelteKit route
A social image URL must be public and return image bytes without a user session. A SvelteKit endpoint can generate those bytes from the same route data. The following example uses the community sveltekit-og package; its API and compatibility are package-specific, so check its current documentation before adopting it. The HTML template and image renderer are examples, not built-in SvelteKit requirements.
// src/routes/api/og/[slug].png/+server.ts
import { error } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { ImageResponse } from 'sveltekit-og';
const cards = {
'dynamic-cards': {
title: 'Dynamic social cards in SvelteKit',
description: 'Build route-specific Open Graph metadata and images.'
}
};
export const GET: RequestHandler = async ({ params }) => {
const card = cards[params.slug as keyof typeof cards];
if (!card) error(404, 'Card not found');
const image = new ImageResponse(
`<div style="width:1200px;height:630px;background:#101827;color:white;padding:64px;font-family:Arial">
<div style="font-size:28px;color:#9fb3d1">SvelteKit guide</div>
<div style="font-size:64px;font-weight:bold;margin-top:50px">${escapeHtml(card.title)}</div>
<div style="font-size:30px;margin-top:24px">${escapeHtml(card.description)}</div>
</div>`
);
return new Response(await image.arrayBuffer(), {
headers: {
'Content-Type': 'image/png',
'Cache-Control': 'public, max-age=3600'
}
});
};
function escapeHtml(value: string) {
return value.replace(/[&<>"']/g, (char) => ({
'&': '&', '<': '<', '>': '>',
'"': '"', "'": '''
})[char]!);
}
The renderer call above is illustrative: confirm the installed package’s actual constructor and response API and adapt the endpoint accordingly. If you use an image library that accepts structured markup or a component, prefer that over string-built HTML. Always escape title and description values before placing them in markup.
Keep the image endpoint’s URL stable and use the same route data source as the page where possible. That prevents a title from appearing on the page while an old or mismatched title remains embedded in its card.
4. Choose prerendering or on-demand generation
| Situation | Approach | Tradeoff |
|---|---|---|
| Finite posts, stable data, static hosting | Prerender pages and image routes at build time; enumerate dynamic route entries. | Fast static delivery and no request-time rendering, but content changes require a rebuild. |
| Frequently changing or long-tail content | Render page metadata and generate image bytes at request time. | Fresh data without enumerating every route; deployment must support server routes and runtime capacity. |
| Personalized or private content | Keep private data out of public metadata and shared image URLs. | Prerendered and publicly scraped cards can expose their contents to anyone. |
SvelteKit’s prerendering model assumes direct users receive the same content. For parameterized routes, provide or discover the entries to prerender. You can set prerender options on the page or endpoint where they apply; avoid turning on prerendering for output that depends on per-request identity or changing private data. See the official prerender documentation.
For runtime generation, use an adapter and hosting environment that supports SvelteKit server endpoints. For static hosting, generate each required image during the build or use a separate image service that can respond at a public URL.
5. Validate the delivered HTML and image
- Deploy or build the app in the same mode used in production.
- Fetch a route’s HTML directly and inspect the response source. Confirm its title, description, canonical URL, and Open Graph tags are present before browser hydration.
- Open the exact
og:imageURL without being logged in. Confirm it returns successfully with an image content type and actual image bytes. - Check at least two slugs, including an unknown slug, to catch route-data mixups and missing-card behavior.
- Use the target platform’s current sharing inspector or debugger to see how that platform reads the deployed page. Platform cache refresh and image rules differ.
For a quick HTML check, replace the example domain and route:
curl -sS https://example.com/posts/dynamic-cards | grep -E 'og:title|og:image|og:url'
Then inspect the image response:
curl -sSI https://example.com/api/og/dynamic-cards.png
6. Options and edge cases
- Canonical URL: Emit the externally accessible canonical URL, not an internal preview hostname. Set the public site origin from trusted deployment configuration if request hosts can vary.
- Missing content: Return a real 404 for unknown slugs, or deliberately use a generic fallback card. Do not accidentally return another post’s metadata.
- Unicode and special characters: Let Svelte escape attribute values. Escape values again if an image renderer consumes HTML strings; HTML escaping and URL encoding solve different problems.
- Image accessibility: Set meaningful
og:image:alttext. Keep it aligned with the actual artwork. - Authentication: Public crawlers generally cannot use your logged-in session. Do not place a session-gated image URL in metadata.
- Stale content: A page and its image may be cached independently. Version image URLs when artwork changes, or use an intentional cache policy and platform refresh tool.
- Content privacy: Do not put account-specific or sensitive data into a public card URL or a prerendered page.
- Build discovery: A dynamic route will not be emitted by a static build unless its concrete entries are discovered or supplied.
7. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Preview shows generic or missing metadata | Tags are added only in browser code, or the crawler got an error response. | Load data in a server-capable function and inspect the raw HTML response. |
| Every route shows the same title or image | Metadata uses shared constants, the wrong slug, or stale cached output. | Trace the slug through load data and image endpoint; verify each route directly and refresh the platform cache. |
| Image URL fails for the crawler | Relative URL, authentication, redirect, or server route unavailable in static deployment. | Use an absolute public URL and verify the endpoint in the deployed environment without cookies. |
| Image downloads but preview has no artwork | Response is HTML/error content, wrong content type, or invalid image bytes. | Check the response status, Content-Type, and body; return actual PNG/JPEG/WebP bytes. |
| Static build reports an unprerendered route | The parameterized image or page route was not enumerated. | Provide route entries or generate the image at runtime on a server-capable adapter. |
| Image has the wrong text or layout | Escaping, font, or renderer limitations; title and image use different records. | Use safe structured rendering, test long and Unicode titles, and share the route data lookup. |
| Updated card is not visible | Browser, CDN, or platform cache still has the old image. | Use a deliberate cache policy, version the image URL when content changes, and use the platform’s current refresh tool. |
8. Performance, reliability, and cost
Prerendering moves image work to the build and serves files as static assets, which suits stable, enumerable content. On-demand generation avoids rebuilding for every content change, but each uncached request depends on the endpoint, its renderer, and the hosting runtime. The research sources provide no measured performance comparison, so choose based on your route count, update frequency, deployment, and latency needs rather than assumed benchmarks.
Cache generated images when the underlying content is stable. Ensure cache keys include every input that changes the image, such as the slug or an explicit version. Bound generation work for long titles and missing assets, and return clear status codes for invalid routes. A social crawler may request a card at a different time from a user; keep the image endpoint reliable independently of the page’s interactive features.
Costs depend on your hosting and image-rendering setup. Static output consumes build and asset storage; runtime output consumes request and compute capacity. Track generation failures and response latency in your normal server monitoring, and avoid generating personalized variants unless the public-sharing behavior is intentional.
9. Or skip the browser setup
If you need a screenshot of a rendered page as a card or preview asset, ScreenshotNeo is a website screenshot API and MCP server. It can return PNG, JPEG, WebP, or PDF from one GET request. For a dynamic Svelte route, pass the public route URL after deployment:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com/posts/dynamic-cards -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://example.com/posts/dynamic-cards"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com/posts/dynamic-cards' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
See the ScreenshotNeo API documentation for request options and response details. Cookie banners, popups, and chat widgets are removed before the shot; bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month, with no card.
FAQ
Can I use this approach in a plain Svelte app?
Only if your server or build process puts the metadata in the initial HTML response. A client-only update after JavaScript runs may not be available to a crawler.
Does generating an image endpoint make the page metadata dynamic?
No. The page still needs to emit its route-specific title, description, canonical URL, and image URL. The endpoint supplies the image bytes referenced by that metadata.
Should every social platform use the same card image?
You can publish one public image URL in Open Graph metadata, but platform-specific image rules differ. Verify the result using each target platform’s current guidance and sharing inspector.
Can social cards contain user-specific content?
A public card URL can be fetched and cached outside the user’s session. Keep private or sensitive information out of share metadata and generated artwork.


