Appearance
Svelte Integration
Generate OG images for SvelteKit applications.
SvelteKit Setup
1. Environment Variable
bash
# .env
OG_IMAGE_API_KEY=og_your_api_key_here2. Create API Endpoint
javascript
// src/routes/api/og/+server.js
import { env } from '$env/dynamic/private';
export async function GET({ url }) {
const title = url.searchParams.get('title') || 'My Site';
const subtitle = url.searchParams.get('subtitle') || '';
const template = url.searchParams.get('template') || 'default';
const response = await fetch('https://ogimageapi.io/api/generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': env.OG_IMAGE_API_KEY
},
body: JSON.stringify({
title,
subtitle,
template,
theme: 'dark'
})
});
const imageBuffer = await response.arrayBuffer();
return new Response(imageBuffer, {
headers: {
'Content-Type': 'image/png',
'Cache-Control': 'public, max-age=86400, s-maxage=86400'
}
});
}3. Create SEO Component
svelte
<!-- src/lib/components/SEO.svelte -->
<script>
import { page } from '$app/stores';
export let title;
export let description = '';
export let image = null;
export let type = 'website';
$: ogImage = image || `/api/og?title=${encodeURIComponent(title)}&subtitle=${encodeURIComponent(description)}`;
$: fullUrl = $page.url.href;
</script>
<svelte:head>
<title>{title}</title>
<meta name="description" content={description} />
<!-- Open Graph -->
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:image" content={ogImage} />
<meta property="og:url" content={fullUrl} />
<meta property="og:type" content={type} />
<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={ogImage} />
</svelte:head>4. Use in Pages
svelte
<!-- src/routes/blog/[slug]/+page.svelte -->
<script>
import SEO from '$lib/components/SEO.svelte';
export let data;
</script>
<SEO
title={data.post.title}
description={data.post.excerpt}
type="article"
/>
<article>
<h1>{data.post.title}</h1>
{@html data.post.content}
</article>Static Site Generation
Generate images at build time for static sites:
Build Script
javascript
// scripts/generate-og.js
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
async function generateOGImages() {
// Get your content
const posts = await getPosts(); // Your data fetching logic
const outputDir = path.join(__dirname, '../static/og');
fs.mkdirSync(outputDir, { recursive: true });
for (const post of posts) {
const response = await fetch('https://ogimageapi.io/api/generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.OG_IMAGE_API_KEY
},
body: JSON.stringify({
title: post.title,
subtitle: post.excerpt,
author_name: post.author,
template: 'blog',
theme: 'dark'
})
});
const buffer = await response.arrayBuffer();
fs.writeFileSync(
path.join(outputDir, `${post.slug}.png`),
Buffer.from(buffer)
);
console.log(`✓ ${post.slug}.png`);
}
}
generateOGImages();Package.json
json
{
"scripts": {
"generate-og": "node scripts/generate-og.js",
"build": "npm run generate-og && vite build"
}
}Load Function Integration
javascript
// src/routes/blog/[slug]/+page.server.js
import { env } from '$env/dynamic/private';
export async function load({ params, fetch }) {
const post = await getPost(params.slug);
// Option 1: Dynamic OG URL
const ogImage = `/api/og?title=${encodeURIComponent(post.title)}&subtitle=${encodeURIComponent(post.excerpt)}`;
// Option 2: Pre-generated static image
// const ogImage = `/og/${params.slug}.png`;
return {
post,
ogImage
};
}Layout-Level SEO
svelte
<!-- src/routes/+layout.svelte -->
<script>
import { page } from '$app/stores';
// Default OG image for pages without custom ones
const defaultOGImage = '/api/og?title=My+Svelte+App&subtitle=Welcome';
</script>
<svelte:head>
<meta property="og:site_name" content="My Svelte App" />
<meta property="og:image" content={$page.data.ogImage || defaultOGImage} />
</svelte:head>
<slot />Prerender Hook
Generate OG images during prerendering:
javascript
// src/hooks.server.js
import { env } from '$env/dynamic/private';
import fs from 'fs';
import path from 'path';
const generatedImages = new Set();
export async function handle({ event, resolve }) {
// During prerendering, generate OG images
if (event.url.pathname.startsWith('/blog/')) {
const slug = event.url.pathname.split('/').pop();
if (!generatedImages.has(slug)) {
await generateOGImage(slug);
generatedImages.add(slug);
}
}
return resolve(event);
}
async function generateOGImage(slug) {
const post = await getPost(slug);
const response = await fetch('https://ogimageapi.io/api/generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': env.OG_IMAGE_API_KEY
},
body: JSON.stringify({
title: post.title,
subtitle: post.excerpt,
template: 'blog',
theme: 'dark'
})
});
const buffer = await response.arrayBuffer();
const outputPath = path.join('./static/og', `${slug}.png`);
fs.writeFileSync(outputPath, Buffer.from(buffer));
}Adapter Configuration
Vercel
javascript
// svelte.config.js
import adapter from '@sveltejs/adapter-vercel';
export default {
kit: {
adapter: adapter({
runtime: 'nodejs18.x'
})
}
};Node
javascript
// svelte.config.js
import adapter from '@sveltejs/adapter-node';
export default {
kit: {
adapter: adapter()
}
};Best Practices
- Use server routes for dynamic OG images
- Generate statically when content is known at build time
- Cache responses — Set appropriate cache headers
- Use layout-level defaults — Fallback for pages without custom OG
- Test with dev tools — Check meta tags are correct