This tutorial was written by Claude Code (an AI) and has not yet been reviewed. Follow along with caution. If the tutorial was helpful or a specific part was not clear/correct, please provide feedback at the bottom of the page. Thank you.
Introduction
SvelteKit is a modern web framework for building high-performance applications with Svelte. It’s an excellent choice for headless storefronts with CoCart because of its speed, developer experience, and flexible rendering options.
This guide will walk you through setting up a SvelteKit project configured to work with CoCart API.
Why SvelteKit for Headless Commerce?
Fast and lightweight - Minimal JavaScript bundle with reactive components
Flexible rendering - Choose between SSR, SSG, or CSR per route
Built-in routing - File-based routing with layouts and nested routes
Server-side capabilities - API routes and server-only code with +server.ts
and +page.server.ts
Great DX - Hot module replacement, TypeScript support, and excellent tooling
Progressive enhancement - Works without JavaScript, enhanced when available
Prerequisites
Node.js 18 or higher
A WordPress site with WooCommerce installed
CoCart plugin installed and activated
Basic knowledge of JavaScript and command line
Creating a New SvelteKit Project
Create a new SvelteKit project using the official CLI:
npm create svelte@latest my-headless-store
When prompted, choose the following options:
Which Svelte app template? → Skeleton project
Add type checking with TypeScript? → Yes, using TypeScript syntax (recommended)
Select additional options →
Add ESLint for code linting (recommended)
Add Prettier for code formatting (recommended)
Navigate to your project:
Install dependencies:
Installing Tailwind CSS
Install Tailwind CSS using the SvelteKit integration:
npx svelte-add@latest tailwindcss
npm install
This will:
Install Tailwind CSS and its dependencies
Create tailwind.config.js
and postcss.config.js
files
Add necessary imports to your app
Project Structure
Your SvelteKit project should have this structure:
my-headless-store/
├── src/
│ ├── lib/
│ │ ├── server/ # Server-only code
│ │ └── cocart.ts # CoCart API client
│ ├── routes/
│ │ ├── +layout.svelte # Root layout
│ │ ├── +page.svelte # Home page
│ │ └── api/ # API routes
│ └── app.html # HTML template
├── static/ # Static assets
├── svelte.config.js # SvelteKit configuration
├── tailwind.config.js # Tailwind configuration
└── package.json
SvelteKit distinguishes between client and server code. Code in src/lib/server
is only bundled for the server and never sent to the client.
Environment Configuration
Create a .env
file in your project root:
PUBLIC_STORE_URL = https://yourstore.com
Variables prefixed with PUBLIC_
are exposed to the client-side code. Keep sensitive data in server-only environment variables without the PUBLIC_
prefix.
Add .env
to your .gitignore
:
echo ".env" >> .gitignore
Create a .env.example
for your team:
# .env.example
PUBLIC_STORE_URL = https://yourstore.com
Creating the CoCart API Client
Create a centralized API client to interact with CoCart. Create src/lib/cocart.ts
:
import { PUBLIC_STORE_URL } from '$env/static/public' ;
const STORE_URL = PUBLIC_STORE_URL || 'https://yourstore.com' ;
const API_BASE = ` ${ STORE_URL } /wp-json/cocart/v2` ;
/**
* Fetch products from CoCart API
*/
export async function getProducts ( params : {
per_page ?: number ;
page ?: number ;
[ key : string ] : any ;
} = {}) {
const queryParams = new URLSearchParams ({
per_page: String ( params . per_page || 12 ),
page: String ( params . page || 1 ),
... Object . fromEntries (
Object . entries ( params ). map (([ k , v ]) => [ k , String ( v )])
)
});
try {
const response = await fetch ( ` ${ API_BASE } /products? ${ queryParams } ` );
if ( ! response . ok ) {
throw new Error ( `HTTP error! status: ${ response . status } ` );
}
const data = await response . json ();
return data ;
} catch ( error ) {
console . error ( 'Error fetching products:' , error );
return [];
}
}
/**
* Get a single product by ID
*/
export async function getProduct ( productId : string | number ) {
try {
const response = await fetch ( ` ${ API_BASE } /products/ ${ productId } ` );
if ( ! response . ok ) {
throw new Error ( `HTTP error! status: ${ response . status } ` );
}
return await response . json ();
} catch ( error ) {
console . error ( 'Error fetching product:' , error );
return null ;
}
}
/**
* Add item to cart
*/
export async function addToCart (
productId : string | number ,
quantity : number = 1 ,
options : Record < string , any > = {}
) {
try {
const response = await fetch ( ` ${ API_BASE } /cart/add-item` , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/json' ,
},
body: JSON . stringify ({
id: productId ,
quantity: quantity ,
... options
})
});
if ( ! response . ok ) {
throw new Error ( `HTTP error! status: ${ response . status } ` );
}
return await response . json ();
} catch ( error ) {
console . error ( 'Error adding to cart:' , error );
throw error ;
}
}
/**
* Get current cart
*/
export async function getCart ( cartKey : string | null = null ) {
const url = cartKey
? ` ${ API_BASE } /cart?cart_key= ${ cartKey } `
: ` ${ API_BASE } /cart` ;
try {
const response = await fetch ( url );
if ( ! response . ok ) {
throw new Error ( `HTTP error! status: ${ response . status } ` );
}
return await response . json ();
} catch ( error ) {
console . error ( 'Error fetching cart:' , error );
return null ;
}
}
/**
* Update cart item quantity
*/
export async function updateCartItem ( itemKey : string , quantity : number ) {
try {
const response = await fetch ( ` ${ API_BASE } /cart/item/ ${ itemKey } ` , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/json' ,
},
body: JSON . stringify ({ quantity })
});
if ( ! response . ok ) {
throw new Error ( `HTTP error! status: ${ response . status } ` );
}
return await response . json ();
} catch ( error ) {
console . error ( 'Error updating cart item:' , error );
throw error ;
}
}
/**
* Remove item from cart
*/
export async function removeCartItem ( itemKey : string ) {
try {
const response = await fetch ( ` ${ API_BASE } /cart/item/ ${ itemKey } ` , {
method: 'DELETE'
});
if ( ! response . ok ) {
throw new Error ( `HTTP error! status: ${ response . status } ` );
}
return await response . json ();
} catch ( error ) {
console . error ( 'Error removing cart item:' , error );
throw error ;
}
}
Creating a Root Layout
Create a root layout at src/routes/+layout.svelte
:
< script lang = "ts" >
import '../app.css' ;
</ script >
< div class = "min-h-screen bg-white dark:bg-zinc-900" >
< slot />
</ div >
Loading Data with Server-Side Rendering
SvelteKit uses +page.server.ts
files to load data on the server before rendering. Create src/routes/+page.server.ts
:
import { getProducts } from '$lib/cocart' ;
import type { PageServerLoad } from './$types' ;
export const load : PageServerLoad = async () => {
const products = await getProducts ({ per_page: 12 });
return {
products
};
};
Then create the page at src/routes/+page.svelte
:
< script lang = "ts" >
import type { PageData } from './$types' ;
export let data : PageData ;
</ script >
< main class = "container mx-auto px-4 py-12" >
< h1 class = "text-3xl font-bold text-zinc-900 dark:text-white mb-8" >
Welcome to Your Headless Store
</ h1 >
< div class = "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3" >
{# each data . products as product }
< div class = "border rounded-lg p-4" >
< h2 class = "font-semibold" > { product . name } </ h2 >
< p class = "text-zinc-600" >
{ product . prices . currency_symbol }{ product . prices . price }
</ p >
</ div >
{/ each }
</ div >
</ main >
SvelteKit automatically generates TypeScript types for your route data. The PageData
type is auto-generated based on your load
function’s return value.
Creating API Routes
SvelteKit supports API routes using +server.ts
files. Create src/routes/api/cart/add/+server.ts
:
import { json } from '@sveltejs/kit' ;
import { addToCart } from '$lib/cocart' ;
import type { RequestHandler } from './$types' ;
export const POST : RequestHandler = async ({ request }) => {
try {
const body = await request . json ();
const { id , quantity = 1 , ... options } = body ;
const result = await addToCart ( id , quantity , options );
return json ( result );
} catch ( error ) {
return json (
{ error: error instanceof Error ? error . message : 'Unknown error' },
{ status: 500 }
);
}
};
Managing Cart State with Stores
SvelteKit works seamlessly with Svelte stores for client-side state management. Create src/lib/stores/cart.ts
:
Cart Store Implementation
import { writable } from 'svelte/store' ;
interface CartItem {
key : string ;
id : number ;
quantity : number ;
name : string ;
price : string ;
}
interface Cart {
items : CartItem [];
itemCount : number ;
total : string ;
}
function createCartStore () {
const { subscribe , set , update } = writable < Cart >({
items: [],
itemCount: 0 ,
total: '0'
});
return {
subscribe ,
set ,
addItem : ( item : CartItem ) => update ( cart => ({
... cart ,
items: [ ... cart . items , item ],
itemCount: cart . itemCount + item . quantity
})),
removeItem : ( itemKey : string ) => update ( cart => {
const newItems = cart . items . filter ( item => item . key !== itemKey );
const newCount = newItems . reduce (( sum , item ) => sum + item . quantity , 0 );
return {
... cart ,
items: newItems ,
itemCount: newCount
};
}),
clear : () => set ({
items: [],
itemCount: 0 ,
total: '0'
})
};
}
export const cart = createCartStore ();
Use the store in your components:
< script lang = "ts" >
import { cart } from '$lib/stores/cart' ;
</ script >
< div >
< p > Cart items: { $ cart . itemCount } </ p >
</ div >
Running Your Project
Start the development server:
Visit http://localhost:5173
to see your store.
Use npm run dev -- --open
to automatically open the browser.
Building for Production
Build your site for production:
Preview the production build:
Deployment Options
SvelteKit supports multiple adapters for different deployment platforms:
Install the Vercel adapter: npm i -D @sveltejs/adapter-vercel
Update svelte.config.js
: import adapter from '@sveltejs/adapter-vercel' ;
export default {
kit: {
adapter: adapter ()
}
} ;
Install the Netlify adapter: npm i -D @sveltejs/adapter-netlify
Update svelte.config.js
: import adapter from '@sveltejs/adapter-netlify' ;
export default {
kit: {
adapter: adapter ()
}
} ;
Install the Cloudflare adapter: npm i -D @sveltejs/adapter-cloudflare
Update svelte.config.js
: import adapter from '@sveltejs/adapter-cloudflare' ;
export default {
kit: {
adapter: adapter ()
}
} ;
Install the Node adapter: npm i -D @sveltejs/adapter-node
Update svelte.config.js
: import adapter from '@sveltejs/adapter-node' ;
export default {
kit: {
adapter: adapter ()
}
} ;
Advanced Configuration
SvelteKit’s form actions provide a way to handle form submissions with progressive enhancement:
// src/routes/cart/+page.server.ts
import type { Actions } from './$types' ;
import { addToCart } from '$lib/cocart' ;
export const actions = {
addToCart : async ({ request }) => {
const data = await request . formData ();
const productId = data . get ( 'productId' );
const quantity = Number ( data . get ( 'quantity' ));
try {
await addToCart ( productId as string , quantity );
return { success: true };
} catch ( error ) {
return { success: false , error: 'Failed to add item' };
}
}
} satisfies Actions ;
Hooks for Global Request Handling
Use src/hooks.server.ts
for global request handling like authentication:
import type { Handle } from '@sveltejs/kit' ;
export const handle : Handle = async ({ event , resolve }) => {
// Add custom headers, handle authentication, etc.
const response = await resolve ( event );
return response ;
};
Troubleshooting
If you encounter CORS errors, you may need to configure WordPress to allow cross-origin requests. See the CORS documentation .
Verify your PUBLIC_STORE_URL
is correct in .env
Ensure CoCart is installed and activated
Check that WooCommerce is configured properly
Test API endpoints directly in your browser or Postman
If you encounter build errors:
Clear the .svelte-kit
directory: rm -rf .svelte-kit
Reinstall dependencies: npm install
Try building again: npm run build
Next Steps
Now that your SvelteKit project is set up with CoCart:
Build product listing pages with dynamic routes
Create a shopping cart page with real-time updates
Implement checkout functionality
Add user authentication with JWT
Optimize for performance with preloading and caching
Resources