Building a Headless WordPress Site with SvelteKit: The Complete 2025 Guide
SvelteKit and WordPress make a powerful combination for building lightning-fast, modern websites. This guide walks through creating a fully headless WordPress setup with SvelteKit as the frontend, complete with best practices for performance, SEO, and developer experience.
Why SvelteKit + WordPress?
🚀 Blazing fast performance (90+ Lighthouse scores)
💡 Reactive UI with Svelte’s compiler optimizations
🔌 Simple WordPress REST API integration
📱 Progressive enhancement out of the box
🛠️ Built-in routing, SSR, and code-splitting
Architecture Overview
WordPress Backend
SvelteKit Frontend
Static/Hybrid Rendering
CDN Edge Delivery. Our YouTube channel; https://www.youtube.com/@easythemestore
Step 1: Set Up WordPress as a Headless CMS
Essential Plugins
- WPGraphQLÂ (Alternative to REST API)
- JWT Authentication (For secure endpoints)
- Advanced Custom Fields (Structured content)
wp-config.php Tweaks
// Enable CORS header("Access-Control-Allow-Origin: *"); header("Access-Control-Allow-Methods: GET"); // Increase API performance define('WP_POST_REVISIONS', 3); define('AUTOSAVE_INTERVAL', 300);
Step 2: Initialize SvelteKit Project
npm create svelte@latest wordpress-frontend cd wordpress-frontend npm install
Install WordPress Client
npm install @apollo/client graphql # For WPGraphQL # OR npm install axios # For REST API
Step 3: Configure SvelteKit for WordPress
src/lib/wordpress.js (REST API Example)
import axios from 'axios'; export const wp = axios.create({ baseURL: 'https://your-wordpress-site.com/wp-json/wp/v2', timeout: 3000, headers: { 'Content-Type': 'application/json' } }); // Helper functions export async function getPosts() { const { data } = await wp.get('/posts?_embed'); return data; }
For WPGraphQL Users
import { ApolloClient, InMemoryCache } from '@apollo/client'; export const client = new ApolloClient({ uri: 'https://your-wordpress-site.com/graphql', cache: new InMemoryCache() });
Step 4: Create Dynamic Routes
src/routes/blog/[slug]/+page.server.js
export async function load({ params }) { const post = await getPostBySlug(params.slug); if (!post) { return { status: 404, error: new Error('Post not found') }; } return { props: { post } }; } async function getPostBySlug(slug) { const { data } = await wp.get(`/posts?slug=${slug}&_embed=1`); return data[0]; }
Corresponding Svelte Component
<script>
export let data;
const { post } = data;
</script>
<article>
<h1>{post.title.rendered}</h1>
{@html post.content.rendered}
</article>Step 5: Implement ISR (Incremental Static Regeneration)
src/routes/blog/+page.server.js
export async function load() { const posts = await getPosts(); return { props: { posts }, // Revalidate every hour cacheControl: { maxAge: 3600, staleWhileRevalidate: 86400 } }; }
Performance Optimizations
1. Image Handling
<script>
import { optimizeWPImage } from '$lib/images';
</script>
<img
src={optimizeWPImage(post.featured_image, { width: 800 })}
alt={post.title.rendered}
loading="lazy"
/>2. CSS/JS Bundle Splitting
// svelte.config.js import adapter from '@sveltejs/adapter-auto'; export default { kit: { adapter: adapter(), prerender: { handleHttpError: 'warn' }, // Enable granular chunking vite: { build: { chunkSizeWarningLimit: 1600 } } } };
3. Prefetching
<a href="/blog/{post.slug}" use:prefetch>
Read More
</a>Advanced Features
1. Preview Mode
// src/routes/api/preview/+server.js export async function POST({ request }) { const { secret, id, slug } = await request.json(); if (secret !== YOUR_SECRET) { return new Response('Invalid token', { status: 401 }); } const post = await getPreviewPost(id || slug); return new Response(JSON.stringify(post)); }
2. Search with Fuse.js
// src/lib/search.js import Fuse from 'fuse.js'; export function createSearchIndex(posts) { return new Fuse(posts, { keys: ['title.rendered', 'excerpt.rendered'], threshold: 0.3 }); }
Deployment Strategies
| Platform | Advantage | Config |
|---|---|---|
| Vercel | Edge Functions | adapter-vercel |
| Netlify | Forms Handling | adapter-netlify |
| Cloudflare | WASM Support | adapter-cloudflare |
# Build command npm run build # Output will be in /build
SEO Best Practices
1. Dynamic Sitemap
// src/routes/sitemap.xml/+server.js export async function GET() { const posts = await getPosts(); return new Response(` <?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> ${posts.map(post => ` <url> <loc>https://yoursite.com/blog/${post.slug}</loc> <lastmod>${new Date(post.modified_gmt).toISOString()}</lastmod> </url> `).join('')} </urlset> `, { headers: { 'Content-Type': 'application/xml' } }); }
2. Schema.org Markup
<script>
import { jsonLd } from '$lib/seo';
</script>
<svelte:head>
{@html jsonLd({
'@context': 'https://schema.org',
'@type': 'BlogPosting',
headline: post.title.rendered,
datePublished: post.date_gmt
})}
</svelte:head>Troubleshooting
Common Issues
CORS Errors
Fix: Install WP CORS plugin or add headers manuallyAPI Timeouts
Solution: Implement SWR cachingDraft Preview Broken
Fix: Use Next.js-style preview mode
Starter Template
Get our production-ready starter:
github.com/sveltekit-wordpress/starter
Final Thoughts
This stack delivers:
- ⚡ 95+ Lighthouse scores
- 🔍 Perfect SEO out of the box
- đź§© Component-driven development
- 🔄 Real-time previews
