Learn how to integrate WordPress and WooCommerce with Astro to create a high-performance, SEO-friendly e-commerce site.
Introduction
Combining the robust content management capabilities of WordPress and the powerful e-commerce features of WooCommerce with the high-performance static site generation of Astro can create an exceptional online store. In this article, we will explore the steps to integrate WordPress and WooCommerce with Astro, leveraging GraphQL and APIs to deliver a seamless and efficient user experience.
Why Choose Astro for Your E-commerce Site?
Astro is a modern web framework that focuses on delivering static HTML with minimal JavaScript, resulting in fast load times and improved performance. Here are some key reasons to choose Astro for your e-commerce site:
- Performance: Astro's static site generation ensures fast load times and a smooth user experience.
- SEO Optimization: By generating static HTML, Astro helps improve your site's SEO rankings.
- Flexibility: Astro can be integrated with various APIs and content management systems, including WordPress and WooCommerce.
- Scalability: Astro's architecture allows your site to handle increased traffic and data loads efficiently.
Integrating WordPress with Astro
To integrate WordPress with Astro, we will use the WordPress REST API or GraphQL API to fetch content and render it in Astro components. Here are the steps to set up this integration:
- Set Up WordPress: Ensure you have a WordPress site set up and running. Install necessary plugins like WPGraphQL to enable GraphQL API.
- Fetch Data: Use Astro's data fetching capabilities to pull content from your WordPress site using the REST API or GraphQL.
- Render Content: Create Astro components to render the fetched content. Utilize Astro's templating system to structure your pages and posts.
Integrating WooCommerce with Astro
WooCommerce can be integrated with Astro to manage products, orders, and other e-commerce functionalities. Here are the steps to integrate WooCommerce with Astro:
- Set Up WooCommerce: Ensure you have WooCommerce installed and configured on your WordPress site. Install necessary plugins like WooGraphQL to enable GraphQL API for WooCommerce.
- Fetch Products: Use Astro to fetch product data from the WooCommerce API. You can use GraphQL to query product information efficiently.
- Render Products: Create Astro components to display product listings, product details, and other e-commerce features on your site.
Advantages of Using GraphQL with Astro
Using GraphQL with Astro provides several benefits:
- Efficient Data Fetching: GraphQL allows you to request only the data you need, reducing the amount of data transferred and improving performance.
- Flexible Queries: GraphQL enables complex queries to fetch related data in a single request, making it easier to manage and display content.
- Improved Developer Experience: GraphQL's schema and type system provide better tooling and error handling, enhancing the development process.
Conclusion
Integrating WordPress and WooCommerce with Astro enables you to build high-performance, SEO-friendly e-commerce sites that leverage the strengths of each platform. By using GraphQL and APIs, you can efficiently fetch and display data, providing a seamless user experience. Whether you're looking to enhance your existing site or build a new one from scratch, Astro offers a powerful solution for modern web development.
Step 2: WordPress and GraphQL Setup
First, let's configure WordPress and WooCommerce for headless operation. Install and activate these plugins:
- WPGraphQL (
wp-graphql
) - WPGraphQL for WooCommerce (
wp-graphql-woocommerce
) - WPGraphQL CORS (
wp-graphql-cors
)
Add this to your WordPress wp-config.php
file:
// Enable GraphQL debugging
define('GRAPHQL_DEBUG', true);
// Allow CORS for your Astro domain
define('GRAPHQL_CORS_ALLOW_ORIGIN', 'http://localhost:3000');
Step 3: Configure Astro for GraphQL
Create a GraphQL client utility file:
// src/lib/graphql.ts
export async function gqlQuery(query: string, variables = {}) {
const response = await fetch('https://your-wordpress-site.com/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query,
variables,
}),
});
const { data } = await response.json();
return data;
}
Step 4: Fetch Products from WooCommerce
Create a products page in Astro:
---
// src/pages/products.astro
import { gqlQuery } from '../lib/graphql';
const PRODUCTS_QUERY = `
query GetProducts {
products(first: 12) {
nodes {
id
name
price
slug
image {
sourceUrl
altText
}
}
}
}
`;
const { products } = await gqlQuery(PRODUCTS_QUERY);
---
<div class="products-grid">
{products.nodes.map((product) => (
<div class="product-card">
<img src={product.image.sourceUrl} alt={product.image.altText} />
<h3>{product.name}</h3>
<p class="price">{product.price}</p>
<button data-product-id={product.id}>Add to Cart</button>
</div>
))}
</div>
<style>
.products-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 2rem;
padding: 2rem;
}
.product-card {
border: 1px solid #eee;
padding: 1rem;
border-radius: 8px;
}
</style>
Step 5: Single Product Page
Create dynamic product pages:
---
// src/pages/product/[slug].astro
import { gqlQuery } from '../../lib/graphql';
export async function getStaticPaths() {
const PATHS_QUERY = `
query GetProductSlugs {
products(first: 100) {
nodes {
slug
}
}
}
`;
const { products } = await gqlQuery(PATHS_QUERY);
return products.nodes.map(({ slug }) => ({
params: { slug },
}));
}
const { slug } = Astro.params;
const PRODUCT_QUERY = `
query GetProduct($slug: ID!) {
product(id: $slug, idType: SLUG) {
id
name
description
price
image {
sourceUrl
altText
}
}
}
`;
const { product } = await gqlQuery(PRODUCT_QUERY, { slug });
---
<div class="product-detail">
<img src={product.image.sourceUrl} alt={product.image.altText} />
<div class="product-info">
<h1>{product.name}</h1>
<p class="price">{product.price}</p>
<div class="description" set:html={product.description} />
<button data-product-id={product.id}>Add to Cart</button>
</div>
</div>
Step 6: Shopping Cart Implementation
Create a cart component using client-side JavaScript:
---
// src/components/Cart.astro
---
<div id="cart" class="cart-drawer">
<h2>Shopping Cart</h2>
<div id="cart-items"></div>
<div id="cart-total"></div>
</div>
<script>
class Cart {
constructor() {
this.items = new Map();
this.loadCart();
this.bindEvents();
}
loadCart() {
const savedCart = localStorage.getItem('cart');
if (savedCart) {
const cartData = JSON.parse(savedCart);
cartData.forEach(([id, item]) => this.items.set(id, item));
}
this.updateCart();
}
addItem(product) {
const existing = this.items.get(product.id);
if (existing) {
existing.quantity += 1;
} else {
this.items.set(product.id, { ...product, quantity: 1 });
}
this.saveCart();
this.updateCart();
}
updateCart() {
const cartEl = document.getElementById('cart-items');
cartEl.innerHTML = Array.from(this.items.values())
.map(item => `
<div class="cart-item">
<img src="${item.image}" alt="${item.name}" />
<h3>${item.name}</h3>
<p>Quantity: ${item.quantity}</p>
<p>Price: ${item.price}</p>
</div>
`).join('');
}
saveCart() {
localStorage.setItem('cart',
JSON.stringify(Array.from(this.items.entries())));
}
bindEvents() {
document.addEventListener('click', e => {
if (e.target.matches('[data-product-id]')) {
const productId = e.target.dataset.productId;
// Fetch product data and add to cart
this.addItem(productData);
}
});
}
}
new Cart();
</script>
<style>
.cart-drawer {
position: fixed;
right: 0;
top: 0;
height: 100vh;
width: 300px;
background: white;
box-shadow: -2px 0 5px rgba(0,0,0,0.1);
padding: 1rem;
}
</style>
Step 7: Deployment Considerations
When deploying your Astro + WordPress site, consider these important factors:
- Configure CORS headers on your WordPress site to allow requests from your Astro domain
- Set up proper caching for GraphQL requests
- Use environment variables for API endpoints
// src/env.d.ts
interface ImportMetaEnv {
readonly WORDPRESS_API_URL: string;
readonly WORDPRESS_AUTH_TOKEN?: string;
}
// astro.config.mjs
export default defineConfig({
vite: {
define: {
'process.env.WORDPRESS_API_URL':
JSON.stringify(process.env.WORDPRESS_API_URL),
},
},
});
Project Structure
Here's how we'll organize our Astro + WordPress e-commerce project:
my-astro-store/
├── src/
│ ├── components/
│ │ ├── Cart.astro
│ │ ├── ProductCard.astro
│ │ └── ProductGrid.astro
│ ├── layouts/
│ │ └── Store.astro
│ ├── lib/
│ │ ├── graphql.ts
│ │ └── woocommerce.ts
│ └── pages/
│ ├── products/
│ │ ├── index.astro
│ │ └── [slug].astro
│ └── cart.astro
├── astro.config.mjs
└── package.json
Environment Setup
Create a .env
file in your project root:
WORDPRESS_URL=https://your-wordpress-site.com
WORDPRESS_AUTH_TOKEN=your_jwt_token
PUBLIC_SITE_URL=http://localhost:3000
Update your astro.config.mjs
:
import { defineConfig } from 'astro/config';
export default defineConfig({
site: process.env.PUBLIC_SITE_URL,
integrations: [
// Add any required integrations
],
vite: {
plugins: [
// Add any Vite plugins
],
define: {
'process.env.WORDPRESS_URL': JSON.stringify(process.env.WORDPRESS_URL),
},
},
});
Creating Reusable Product Components
ProductCard Component
---
// src/components/ProductCard.astro
interface Props {
product: {
id: string;
name: string;
price: string;
image: {
sourceUrl: string;
altText: string;
};
slug: string;
};
}
const { product } = Astro.props;
---
<div class="product-card">
<a href={'/products/' + product.slug}>
<img
src={product.image.sourceUrl}
alt={product.image.altText}
loading="lazy"
width="300"
height="300"
/>
<h3>{product.name}</h3>
<p class="price">{product.price}</p>
</a>
<button
class="add-to-cart"
data-product-id={product.id}
data-product-name={product.name}
data-product-price={product.price}
data-product-image={product.image.sourceUrl}
>
Add to Cart
</button>
</div>
<style>
.product-card {
border: 1px solid #eee;
border-radius: 8px;
padding: 1rem;
transition: transform 0.2s;
}
.product-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.product-card img {
width: 100%;
height: auto;
border-radius: 4px;
}
.price {
font-weight: bold;
color: #2563eb;
}
.add-to-cart {
width: 100%;
padding: 0.5rem;
background: #2563eb;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background 0.2s;
}
.add-to-cart:hover {
background: #1d4ed8;
}
</style>
Enhanced Cart Implementation
---
// src/components/Cart.astro
---
<div id="cart" class="cart-drawer">
<div class="cart-header">
<h2>Shopping Cart</h2>
<button id="close-cart">×</button>
</div>
<div id="cart-items"></div>
<div id="cart-total"></div>
<button id="checkout" class="checkout-button">Proceed to Checkout</button>
</div>
<script>
interface CartItem {
id: string;
name: string;
price: number;
quantity: number;
image: string;
}
class Cart {
private items: Map<string, CartItem>;
private isOpen: boolean;
constructor() {
this.items = new Map();
this.isOpen = false;
this.loadCart();
this.bindEvents();
}
private loadCart() {
const savedCart = localStorage.getItem('cart');
if (savedCart) {
const cartData = JSON.parse(savedCart);
cartData.forEach(([id, item]: [string, CartItem]) =>
this.items.set(id, item)
);
}
this.updateCart();
}
private addItem(productData: CartItem) {
const existing = this.items.get(productData.id);
if (existing) {
existing.quantity += 1;
} else {
this.items.set(productData.id, {
...productData,
quantity: 1
});
}
this.saveCart();
this.updateCart();
this.openCart();
}
private removeItem(id: string) {
this.items.delete(id);
this.saveCart();
this.updateCart();
}
private updateQuantity(id: string, quantity: number) {
const item = this.items.get(id);
if (item) {
item.quantity = quantity;
if (quantity <= 0) {
this.removeItem(id);
} else {
this.saveCart();
this.updateCart();
}
}
}
private updateCart() {
const cartEl = document.getElementById('cart-items');
const totalEl = document.getElementById('cart-total');
if (!cartEl || !totalEl) return;
cartEl.innerHTML = Array.from(this.items.values())
.map(item => `
<div class="cart-item" data-id="${item.id}">
<img src="${item.image}" alt="${item.name}" />
<div class="cart-item-details">
<h3>${item.name}</h3>
<div class="quantity-controls">
<button class="quantity-btn minus">-</button>
<input type="number" value="${item.quantity}"
min="1" class="quantity-input" />
<button class="quantity-btn plus">+</button>
</div>
<p class="price">$${(item.price * item.quantity).toFixed(2)}</p>
</div>
<button class="remove-item">×</button>
</div>
`).join('');
const total = Array.from(this.items.values())
.reduce((sum, item) => sum + (item.price * item.quantity), 0);
totalEl.innerHTML = `
<div class="cart-total">
<span>Total:</span>
<span>$${total.toFixed(2)}</span>
</div>
`;
}
private saveCart() {
localStorage.setItem('cart',
JSON.stringify(Array.from(this.items.entries()))
);
}
private openCart() {
const cart = document.getElementById('cart');
if (cart) {
cart.classList.add('open');
this.isOpen = true;
}
}
private closeCart() {
const cart = document.getElementById('cart');
if (cart) {
cart.classList.remove('open');
this.isOpen = false;
}
}
private bindEvents() {
// Add to cart button clicks
document.addEventListener('click', e => {
const target = e.target as HTMLElement;
if (target.matches('[data-product-id]')) {
const button = target as HTMLButtonElement;
const productData = {
id: button.dataset.productId!,
name: button.dataset.productName!,
price: parseFloat(button.dataset.productPrice!),
image: button.dataset.productImage!
};
this.addItem(productData);
}
});
// Cart item quantity controls
document.addEventListener('click', e => {
const target = e.target as HTMLElement;
if (target.matches('.quantity-btn')) {
const cartItem = target.closest('.cart-item');
if (!cartItem) return;
const id = cartItem.dataset.id!;
const input = cartItem.querySelector('.quantity-input') as HTMLInputElement;
const currentQty = parseInt(input.value);
if (target.classList.contains('plus')) {
this.updateQuantity(id, currentQty + 1);
} else if (target.classList.contains('minus')) {
this.updateQuantity(id, currentQty - 1);
}
}
});
// Remove item button clicks
document.addEventListener('click', e => {
const target = e.target as HTMLElement;
if (target.matches('.remove-item')) {
const cartItem = target.closest('.cart-item');
if (cartItem) {
this.removeItem(cartItem.dataset.id!);
}
}
});
// Close cart button
document.getElementById('close-cart')?.addEventListener('click', () => {
this.closeCart();
});
// Checkout button
document.getElementById('checkout')?.addEventListener('click', () => {
// Implement checkout logic here
console.log('Proceeding to checkout with items:',
Array.from(this.items.values()));
});
}
}
// Initialize cart
new Cart();
</script>
<style>
.cart-drawer {
position: fixed;
right: -400px;
top: 0;
height: 100vh;
width: 400px;
background: white;
box-shadow: -2px 0 5px rgba(0,0,0,0.1);
padding: 1rem;
transition: right 0.3s ease;
z-index: 1000;
}
.cart-drawer.open {
right: 0;
}
.cart-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
padding-bottom: 1rem;
border-bottom: 1px solid #eee;
}
.cart-item {
display: grid;
grid-template-columns: auto 1fr auto;
gap: 1rem;
padding: 1rem;
border-bottom: 1px solid #eee;
}
.cart-item img {
width: 60px;
height: 60px;
object-fit: cover;
border-radius: 4px;
}
.quantity-controls {
display: flex;
align-items: center;
gap: 0.5rem;
}
.quantity-input {
width: 50px;
text-align: center;
}
.quantity-btn {
width: 24px;
height: 24px;
border-radius: 50%;
border: 1px solid #ddd;
background: white;
cursor: pointer;
}
.checkout-button {
width: 100%;
padding: 1rem;
background: #2563eb;
color: white;
border: none;
border-radius: 4px;
margin-top: 1rem;
cursor: pointer;
}
.checkout-button:hover {
background: #1d4ed8;
}
</style>
Enhanced GraphQL Integration
Create a more robust GraphQL client with caching and error handling:
// src/lib/graphql.ts
interface GraphQLResponse<T> {
data?: T;
errors?: Array<{
message: string;
locations: Array<{
line: number;
column: number;
}>;
}>;
}
class GraphQLClient {
private endpoint: string;
private cache: Map<string, any>;
private cacheTimeout: number;
constructor(endpoint: string, cacheTimeout = 5 * 60 * 1000) {
this.endpoint = endpoint;
this.cache = new Map();
this.cacheTimeout = cacheTimeout;
}
async query<T>(query: string, variables = {}, useCache = true): Promise<T> {
const cacheKey = JSON.stringify({ query, variables });
if (useCache) {
const cached = this.cache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < this.cacheTimeout) {
return cached.data;
}
}
try {
const response = await fetch(this.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
// Add any authentication headers here
},
body: JSON.stringify({
query,
variables,
}),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result: GraphQLResponse<T> = await response.json();
if (result.errors) {
throw new Error(result.errors[0].message);
}
if (!result.data) {
throw new Error('No data returned from GraphQL query');
}
if (useCache) {
this.cache.set(cacheKey, {
data: result.data,
timestamp: Date.now(),
});
}
return result.data;
} catch (error) {
console.error('GraphQL query error:', error);
throw error;
}
}
clearCache() {
this.cache.clear();
}
}
// Create a singleton instance
export const graphql = new GraphQLClient(
import.meta.env.WORDPRESS_URL + '/graphql'
);
// Export common queries
export const PRODUCT_QUERIES = {
ALL_PRODUCTS: `
query GetProducts($first: Int = 12, $after: String) {
products(first: $first, after: $after) {
pageInfo {
hasNextPage
endCursor
}
nodes {
id
name
price
slug
shortDescription
image {
sourceUrl
altText
}
... on SimpleProduct {
price
regularPrice
salePrice
}
... on VariableProduct {
variations {
nodes {
price
regularPrice
salePrice
}
}
}
}
}
}
`,
SINGLE_PRODUCT: `
query GetProduct($slug: ID!) {
product(id: $slug, idType: SLUG) {
id
name
description
shortDescription
price
regularPrice
salePrice
slug
image {
sourceUrl
altText
}
galleryImages {
nodes {
sourceUrl
altText
}
}
... on SimpleProduct {
stockStatus
stockQuantity
}
... on VariableProduct {
variations {
nodes {
id
name
price
regularPrice
salePrice
stockStatus
stockQuantity
attributes {
nodes {
name
value
}
}
}
}
}
}
}
`,
};
Final Thoughts
By combining Astro with WordPress and WooCommerce, you get the best of both worlds: WordPress's content management and e-commerce capabilities with Astro's performance benefits. This architecture provides:
- Blazing-fast page loads with static site generation
- Robust content management through WordPress
- Powerful e-commerce features via WooCommerce
- Improved SEO through static HTML generation