How to Add SEO to a SvelteKit Project

SEO (Search Engine Optimization) is crucial for any web application that wants to be discoverable on search engines. SvelteKit, with its server-side rendering capabilities, provides an excellent foundation for building SEO-friendly applications.
In this comprehensive guide, I'll walk you through the essential steps to implement SEO in your SvelteKit project, from basic meta tags to advanced optimization techniques.
Why SEO Matters
SEO isn't just about rankings—it's about making your content accessible, fast, and user-friendly. With SvelteKit's built-in SSR capabilities, you get the performance benefits of a SPA while maintaining the SEO advantages of server-rendered content.
- Better search engine visibility and rankings
- Improved social media sharing with rich previews
- Enhanced user experience and accessibility
Essential SEO Components
Let's explore the key components you need to implement for effective SEO in SvelteKit.
Meta Tags
Title, description, keywords, and Open Graph tags
Structured Data
JSON-LD schema markup for rich snippets
Sitemap
XML sitemap for search engine crawling
Performance
Core Web Vitals and loading optimization
🚀Implementation Guide
Let's implement a complete SEO solution for SvelteKit that covers meta tags, structured data, and performance optimization.
Step 1: Create SEO Component
<!-- src/lib/components/SEO.svelte -->
<script lang="ts">
export let title: string = 'Default Title';
export let description: string = 'Default description';
export let keywords: string = '';
export let image: string = '/default-og-image.jpg';
export let url: string = '';
export let type: string = 'website';
export let structuredData: object | null = null;
$: pageTitle = title + ' | Your Site Name';
</script>
<svelte:head>
<!-- Basic Meta Tags -->
<title>{pageTitle}</title>
<meta name="description" content={description} />
<meta name="keywords" content={keywords} />
<!-- Open Graph -->
<meta property="og:title" content={pageTitle} />
<meta property="og:description" content={description} />
<meta property="og:image" content={image} />
<meta property="og:url" content={url} />
<meta property="og:type" content={type} />
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={pageTitle} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={image} />
<!-- Canonical URL -->
<link rel="canonical" href={url} />
<!-- Structured Data -->
{#if structuredData}
{@html `<script type="application/ld+json">${JSON.stringify(structuredData)}</script>`}
{/if}
</svelte:head>Step 2: Page-Level SEO
<!-- src/routes/+page.svelte -->
<script lang="ts">
import SEO from '$lib/components/SEO.svelte';
import { page } from '$app/stores';
const structuredData = {
"@context": "https://schema.org",
"@type": "WebSite",
"name": "Your Site Name",
"url": "https://yoursite.com"
};
</script>
<SEO
title="Welcome to Our Site"
description="This is the best site for amazing content and resources."
keywords="sveltekit, seo, web development"
image="https://yoursite.com/og-image.jpg"
url={$page.url.href}
{structuredData}
/>
<h1>Welcome to Our Site</h1>
<!-- Rest of your page content -->Step 3: Dynamic SEO with Load Functions
// src/routes/blog/[slug]/+page.server.ts
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ params }) => {
const post = await getPostBySlug(params.slug);
if (!post) {
throw error(404, 'Post not found');
}
return {
post,
seo: {
title: post.title,
description: post.excerpt,
image: post.featured_image,
type: 'article',
structuredData: {
"@context": "https://schema.org",
"@type": "BlogPosting",
"headline": post.title,
"description": post.excerpt,
"image": post.featured_image,
"author": {
"@type": "Person",
"name": post.author
},
"datePublished": post.published_at,
"dateModified": post.updated_at
}
}
};
};Step 4: Generate Sitemap
// src/routes/sitemap.xml/+server.ts
import type { RequestHandler } from './$types';
export const GET: RequestHandler = async () => {
const posts = await getAllPosts();
const pages = ['', 'about', 'contact', 'blog'];
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${pages.map(page => `
<url>
<loc>https://yoursite.com/${page}</loc>
<lastmod>${new Date().toISOString()}</lastmod>
<changefreq>weekly</changefreq>
<priority>${page === '' ? '1.0' : '0.8'}</priority>
</url>
`).join('')}
${posts.map(post => `
<url>
<loc>https://yoursite.com/blog/${post.slug}</loc>
<lastmod>${post.updated_at}</lastmod>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
`).join('')}
</urlset>`;
return new Response(sitemap, {
headers: {
'Content-Type': 'application/xml'
}
});
};Step 5: robots.txt
// src/routes/robots.txt/+server.ts
import type { RequestHandler } from './$types';
export const GET: RequestHandler = async () => {
const robots = `User-agent: *
Allow: /
Sitemap: https://yoursite.com/sitemap.xml`;
return new Response(robots, {
headers: {
'Content-Type': 'text/plain'
}
});
};Step 6: Performance Optimization
// vite.config.js
import { sveltekit } from '@sveltejs/kit/vite';
const config = {
plugins: [sveltekit()],
build: {
minify: 'terser',
rollupOptions: {
output: {
manualChunks: {
vendor: ['svelte']
}
}
}
}
};
export default config;Results
With this setup, your SvelteKit app will have:
- Dynamic meta tags for every page
- Rich structured data for better search results
- Automatic sitemap generation
- Optimized performance and Core Web Vitals