easythemestore

Building a Headless WordPress Site with SvelteKit

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

  1. WPGraphQL (Alternative to REST API)
  2. JWT Authentication (For secure endpoints)
  3. 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

PlatformAdvantageConfig
VercelEdge Functionsadapter-vercel
NetlifyForms Handlingadapter-netlify
CloudflareWASM Supportadapter-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

  1. CORS Errors
    Fix: Install WP CORS plugin or add headers manually

  2. API Timeouts
    Solution: Implement SWR caching

  3. Draft 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