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
Astro is a modern web framework that delivers fast, content-focused websites. It’s perfect for building headless storefronts with CoCart because of its excellent performance, flexibility, and support for multiple frameworks.
This guide will walk you through setting up an Astro project configured to work with CoCart API.
Why Astro for Headless Commerce?
- Fast by default - Ships zero JavaScript by default, loading JS only when needed
- Framework agnostic - Use React, Vue, Svelte, or vanilla JavaScript
- SEO friendly - Server-side rendering and static site generation
- Island architecture - Interactive components only where needed
- Great DX - Hot module replacement and TypeScript support
Prerequisites
- Node.js 18.17.1 or higher
- A WordPress site with WooCommerce installed
- CoCart plugin installed and activated
- Basic knowledge of JavaScript and command line
Creating a New Astro Project
Create a new Astro project using the official CLI:
npm create astro@latest my-headless-store
When prompted, choose the following options:
- How would you like to start? → Empty
- Install dependencies? → Yes
- Initialize git repository? → Yes (recommended)
- TypeScript? → Yes (recommended) or No
Navigate to your project:
Installing Tailwind CSS
Most UI component libraries (including OxbowUI) use Tailwind CSS. Install it using Astro’s integration:
This will:
- Install Tailwind CSS and its dependencies
- Create a
tailwind.config.mjs
file
- Update your Astro configuration
- Add necessary imports
Project Structure
Your Astro project should have this structure:
my-headless-store/
├── src/
│ ├── components/ # Reusable components
│ ├── layouts/ # Page layouts
│ ├── pages/ # Routes (file-based routing)
│ ├── lib/ # Utility functions and API clients
│ └── styles/ # Global styles
├── public/ # Static assets
├── astro.config.mjs # Astro configuration
├── tailwind.config.mjs # Tailwind configuration
├── package.json
└── tsconfig.json # TypeScript config (if using TS)
Create the necessary folders:
mkdir -p src/components src/lib src/layouts
Creating the CoCart API Client
Create a centralized API client to interact with CoCart. Create src/lib/cocart.js
:
We are currently building out this client, so for now just make standard fetch requests to the CoCart API endpoints as needed.
const STORE_URL = import.meta.env.PUBLIC_STORE_URL || 'https://yourstore.com';
const API_BASE = `${STORE_URL}/wp-json/cocart/v2`;
/**
* Fetch products from CoCart API
* @param {Object} params - Query parameters
* @returns {Promise<Array>} Products array
*/
export async function getProducts(params = {}) {
const queryParams = new URLSearchParams({
per_page: params.per_page || 12,
page: params.page || 1,
...params
});
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
* @param {string|number} productId - Product ID
* @returns {Promise<Object>} Product object
*/
export async function getProduct(productId) {
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
* @param {string} productId - Product ID
* @param {number} quantity - Quantity to add
* @param {Object} options - Additional options (variation_id, cart_item_data, etc.)
* @returns {Promise<Object>} Cart response
*/
export async function addToCart(productId, quantity = 1, options = {}) {
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
* @param {string} cartKey - Optional cart key
* @returns {Promise<Object>} Cart object
*/
export async function getCart(cartKey = 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
* @param {string} itemKey - Cart item key
* @param {number} quantity - New quantity
* @returns {Promise<Object>} Updated cart
*/
export async function updateCartItem(itemKey, quantity) {
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
* @param {string} itemKey - Cart item key
* @returns {Promise<Object>} Updated cart
*/
export async function removeCartItem(itemKey) {
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;
}
}
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. Be careful not to expose sensitive data.
Add .env
to your .gitignore
:
echo ".env" >> .gitignore
Create a .env.example
for your team:
# .env.example
PUBLIC_STORE_URL=https://yourstore.com
Creating a Base Layout
Create a base layout at src/layouts/Layout.astro
:
---
interface Props {
title: string;
description?: string;
}
const { title, description = "Your headless WooCommerce store" } = Astro.props;
---
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="description" content={description} />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="generator" content={Astro.generator} />
<title>{title}</title>
</head>
<body class="bg-white dark:bg-zinc-900">
<slot />
</body>
</html>
Adding AlpineJS for Interactivity
If you plan to use interactive components (like OxbowUI), install AlpineJS:
You can initialize it globally in your layout or per-component. For global initialization, update your layout:
---
// src/layouts/Layout.astro
interface Props {
title: string;
description?: string;
}
const { title, description = "Your headless WooCommerce store" } = Astro.props;
---
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="description" content={description} />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="generator" content={Astro.generator} />
<title>{title}</title>
</head>
<body class="bg-white dark:bg-zinc-900">
<slot />
<script>
import Alpine from 'alpinejs';
window.Alpine = Alpine;
Alpine.start();
</script>
</body>
</html>
Alternatively, you can use AlpineJS via CDN by adding this to your <head>
:
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
Creating API Endpoints
Astro supports API routes for server-side operations. Create API endpoints for cart operations.
Create src/pages/api/cart/add.js
:
import { addToCart } from '../../../lib/cocart';
export async function POST({ request }) {
try {
const body = await request.json();
const { id, quantity = 1, ...options } = body;
const result = await addToCart(id, quantity, options);
return new Response(JSON.stringify(result), {
status: 200,
headers: {
'Content-Type': 'application/json'
}
});
} catch (error) {
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
headers: {
'Content-Type': 'application/json'
}
});
}
}
Testing Your Setup
Create a test page at src/pages/index.astro
:
---
import Layout from '../layouts/Layout.astro';
import { getProducts } from '../lib/cocart';
const products = await getProducts({ per_page: 3 });
---
<Layout title="My Headless Store">
<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">
{products.map((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>
))}
</div>
</main>
</Layout>
Running Your Project
Start the development server:
Visit http://localhost:4321
to see your store.
Building for Production
Build your site for production:
Preview the production build:
Deployment Options
Astro sites can be deployed to various platforms:
- Vercel - Zero configuration deployment
- Netlify - Easy deployment with built-in features
- Cloudflare Pages - Global edge network
- GitHub Pages - Free hosting for static sites
- Your own server - Deploy the
dist
folder
Next Steps
Now that your Astro project is set up with CoCart:
- Build product listings with OxbowUI components
- Add shopping cart functionality
- Implement checkout flow
- Add user authentication
- Optimize for performance and SEO
Troubleshooting
CORS Errors
If you encounter CORS errors, you may need to configure WordPress to allow cross-origin requests. See CORS documentation.
API Connection Issues
- Verify your
PUBLIC_STORE_URL
is correct
- Ensure CoCart is installed and activated
- Check that WooCommerce is configured properly
- Test API endpoints directly in your browser or Postman
Resources