Documentation Index Fetch the complete documentation index at: https://docs.cocartapi.com/llms.txt
Use this file to discover all available pages before exploring further.
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
Nuxt is a powerful Vue framework that makes web development intuitive and performant. It’s an excellent choice for building headless storefronts with CoCart because of its server-side rendering capabilities, excellent developer experience, and robust ecosystem.
This guide will walk you through setting up a Nuxt project configured to work with CoCart API. Instructions are provided for both Nuxt 3 (stable, widely supported) and Nuxt 4 (latest release with new features).
Which version should you use?
Nuxt 3 : Recommended for production applications. Stable with extensive ecosystem support and long-term maintenance until January 2026.
Nuxt 4 : Latest features and improvements. Good for new projects that want cutting-edge capabilities. Officially released in 2025.
Why Nuxt for Headless Commerce?
Hybrid rendering - Choose between SSR, SSG, or CSR per route
Auto imports - Components, composables, and utilities are automatically imported
File-based routing - Intuitive routing system with dynamic routes
Server routes - Built-in API routes with full-stack capabilities
Vue ecosystem - Access to the entire Vue.js ecosystem and components
SEO friendly - Built-in SEO features and meta tag management
Prerequisites
Node.js 16.10.0 or higher (18.0.0+ recommended)
A WordPress site with WooCommerce installed
CoCart plugin installed and activated
Basic knowledge of JavaScript and command line
Node.js 18.0.0 or higher
A WordPress site with WooCommerce installed
CoCart plugin installed and activated
Basic knowledge of JavaScript and command line
Creating a New Nuxt Project
Create a new Nuxt 3 project using the official CLI: npx nuxi@latest init my-headless-store
When prompted, choose your preferred package manager (npm, yarn, or pnpm). Navigate to your project: Install dependencies: Create a new Nuxt 4 project using the official CLI: npx nuxi@latest init my-headless-store
When prompted, choose your preferred package manager (npm, yarn, or pnpm). Navigate to your project: Install dependencies: Nuxt 4 uses the same initialization command as Nuxt 3. The latest version will be installed automatically.
Installing Tailwind CSS
Install Tailwind CSS using the Nuxt module:
npm install -D @nuxtjs/tailwindcss
Add the module to your nuxt.config.ts:
export default defineNuxtConfig ({
modules: [ '@nuxtjs/tailwindcss' ] ,
devtools: { enabled: true }
})
export default defineNuxtConfig ({
modules: [ '@nuxtjs/tailwindcss' ] ,
compatibilityDate: '2024-11-01' ,
devtools: { enabled: true }
})
Nuxt 4 introduces the compatibilityDate option for managing framework updates and breaking changes.
Create a tailwind.config.js file (optional, for customization):
/** @type {import('tailwindcss').Config} */
export default {
content: [] ,
theme: {
extend: {},
} ,
plugins: [] ,
}
Project Structure
Your Nuxt project should have this structure:
my-headless-store/
├── assets/ # Stylesheets, fonts, images
├── components/ # Vue components (auto-imported)
├── composables/ # Vue composables (auto-imported)
├── layouts/ # Layout components
├── pages/ # File-based routing
├── public/ # Static files served at root
├── server/
│ ├── api/ # API routes
│ └── utils/ # Server utilities
├── utils/ # Auto-imported utilities
├── app.vue # Main app component
├── nuxt.config.ts # Nuxt configuration
└── package.json
Nuxt automatically imports components from the components/ directory and composables from the composables/ directory. No need for manual imports!
Environment Configuration
Create a .env file in your project root:
NUXT_PUBLIC_STORE_URL = https://yourstore.com
Variables prefixed with NUXT_PUBLIC_ are exposed to the client-side code. Keep sensitive data in server-only environment variables without the NUXT_PUBLIC_ prefix.
Add .env to your .gitignore:
echo ".env" >> .gitignore
Create a .env.example for your team:
# .env.example
NUXT_PUBLIC_STORE_URL = https://yourstore.com
Creating the CoCart Composable
Create a composable to interact with CoCart. Create composables/useCoCart.js:
export const useCoCart = () => {
const config = useRuntimeConfig ()
const STORE_URL = config . public . storeUrl
const API_BASE = ` ${ STORE_URL } /wp-json/cocart/v2`
/**
* Fetch products from CoCart API
*/
const getProducts = async ( params = {}) => {
const queryParams = {
per_page: params . per_page || 12 ,
page: params . page || 1 ,
... params
}
try {
const { data , error } = await useFetch ( ` ${ API_BASE } /products` , {
query: queryParams
})
if ( error . value ) {
console . error ( 'Error fetching products:' , error . value )
return []
}
return data . value || []
} catch ( err ) {
console . error ( 'Error fetching products:' , err )
return []
}
}
/**
* Get a single product by ID
*/
const getProduct = async ( productId ) => {
try {
const { data , error } = await useFetch ( ` ${ API_BASE } /products/ ${ productId } ` )
if ( error . value ) {
console . error ( 'Error fetching product:' , error . value )
return null
}
return data . value
} catch ( err ) {
console . error ( 'Error fetching product:' , err )
return null
}
}
/**
* Add item to cart
*/
const addToCart = async ( productId , quantity = 1 , options = {}) => {
try {
const { data , error } = await useFetch ( ` ${ API_BASE } /cart/add-item` , {
method: 'POST' ,
body: {
id: productId ,
quantity: quantity ,
... options
}
})
if ( error . value ) {
throw new Error ( error . value . message || 'Failed to add item to cart' )
}
return data . value
} catch ( err ) {
console . error ( 'Error adding to cart:' , err )
throw err
}
}
/**
* Get current cart
*/
const getCart = async ( cartKey = null ) => {
const url = cartKey
? ` ${ API_BASE } /cart?cart_key= ${ cartKey } `
: ` ${ API_BASE } /cart`
try {
const { data , error } = await useFetch ( url )
if ( error . value ) {
console . error ( 'Error fetching cart:' , error . value )
return null
}
return data . value
} catch ( err ) {
console . error ( 'Error fetching cart:' , err )
return null
}
}
/**
* Update cart item quantity
*/
const updateCartItem = async ( itemKey , quantity ) => {
try {
const { data , error } = await useFetch ( ` ${ API_BASE } /cart/item/ ${ itemKey } ` , {
method: 'POST' ,
body: { quantity }
})
if ( error . value ) {
throw new Error ( error . value . message || 'Failed to update cart item' )
}
return data . value
} catch ( err ) {
console . error ( 'Error updating cart item:' , err )
throw err
}
}
/**
* Remove item from cart
*/
const removeCartItem = async ( itemKey ) => {
try {
const { data , error } = await useFetch ( ` ${ API_BASE } /cart/item/ ${ itemKey } ` , {
method: 'DELETE'
})
if ( error . value ) {
throw new Error ( error . value . message || 'Failed to remove cart item' )
}
return data . value
} catch ( err ) {
console . error ( 'Error removing cart item:' , err )
throw err
}
}
return {
getProducts ,
getProduct ,
addToCart ,
getCart ,
updateCartItem ,
removeCartItem
}
}
export const useCoCart = () => {
const config = useRuntimeConfig ()
const STORE_URL = config . public . storeUrl
const API_BASE = ` ${ STORE_URL } /wp-json/cocart/v2`
/**
* Fetch products from CoCart API
*/
const getProducts = async ( params = {}) => {
const queryParams = {
per_page: params . per_page || 12 ,
page: params . page || 1 ,
... params
}
try {
const { data , error } = await useFetch ( ` ${ API_BASE } /products` , {
query: queryParams
})
if ( error . value ) {
console . error ( 'Error fetching products:' , error . value )
return []
}
return data . value || []
} catch ( err ) {
console . error ( 'Error fetching products:' , err )
return []
}
}
/**
* Get a single product by ID
*/
const getProduct = async ( productId ) => {
try {
const { data , error } = await useFetch ( ` ${ API_BASE } /products/ ${ productId } ` )
if ( error . value ) {
console . error ( 'Error fetching product:' , error . value )
return null
}
return data . value
} catch ( err ) {
console . error ( 'Error fetching product:' , err )
return null
}
}
/**
* Add item to cart
*/
const addToCart = async ( productId , quantity = 1 , options = {}) => {
try {
const { data , error } = await useFetch ( ` ${ API_BASE } /cart/add-item` , {
method: 'POST' ,
body: {
id: productId ,
quantity: quantity ,
... options
}
})
if ( error . value ) {
throw new Error ( error . value . message || 'Failed to add item to cart' )
}
return data . value
} catch ( err ) {
console . error ( 'Error adding to cart:' , err )
throw err
}
}
/**
* Get current cart
*/
const getCart = async ( cartKey = null ) => {
const url = cartKey
? ` ${ API_BASE } /cart?cart_key= ${ cartKey } `
: ` ${ API_BASE } /cart`
try {
const { data , error } = await useFetch ( url )
if ( error . value ) {
console . error ( 'Error fetching cart:' , error . value )
return null
}
return data . value
} catch ( err ) {
console . error ( 'Error fetching cart:' , err )
return null
}
}
/**
* Update cart item quantity
*/
const updateCartItem = async ( itemKey , quantity ) => {
try {
const { data , error } = await useFetch ( ` ${ API_BASE } /cart/item/ ${ itemKey } ` , {
method: 'POST' ,
body: { quantity }
})
if ( error . value ) {
throw new Error ( error . value . message || 'Failed to update cart item' )
}
return data . value
} catch ( err ) {
console . error ( 'Error updating cart item:' , err )
throw err
}
}
/**
* Remove item from cart
*/
const removeCartItem = async ( itemKey ) => {
try {
const { data , error } = await useFetch ( ` ${ API_BASE } /cart/item/ ${ itemKey } ` , {
method: 'DELETE'
})
if ( error . value ) {
throw new Error ( error . value . message || 'Failed to remove cart item' )
}
return data . value
} catch ( err ) {
console . error ( 'Error removing cart item:' , err )
throw err
}
}
return {
getProducts ,
getProduct ,
addToCart ,
getCart ,
updateCartItem ,
removeCartItem
}
}
The composable code is identical for both Nuxt 3 and 4. Nuxt 4 maintains backward compatibility with Nuxt 3 APIs.
This composable is automatically imported in all your components and pages. Just call const { getProducts } = useCoCart() to use it!
Creating a Default Layout
Create a default layout at layouts/default.vue:
< template >
< div class = "min-h-screen bg-white dark:bg-zinc-900" >
< slot / >
</ div >
</ template >
Creating Your First Page
Create a home page at pages/index.vue:
< script setup >
const { getProducts } = useCoCart ()
// Fetch products on the server
const { data : products } = await useAsyncData ( 'products' , () =>
getProducts ({ per_page: 12 })
)
</ script >
< template >
< NuxtLayout >
< 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" >
< div
v-for = " product in products "
: key = " product . id "
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 >
</ NuxtLayout >
</ template >
The useAsyncData composable automatically handles server-side rendering and client-side hydration. Data fetched on the server is serialized and sent to the client.
Creating Server API Routes
Nuxt supports server API routes for server-side operations. Create server/api/cart/add.post.js:
export default defineEventHandler ( async ( event ) => {
try {
const body = await readBody ( event )
const { id , quantity = 1 , ... options } = body
const config = useRuntimeConfig ()
const STORE_URL = config . public . storeUrl
const API_BASE = ` ${ STORE_URL } /wp-json/cocart/v2`
const response = await $fetch ( ` ${ API_BASE } /cart/add-item` , {
method: 'POST' ,
body: {
id ,
quantity ,
... options
}
})
return response
} catch ( error ) {
throw createError ({
statusCode: 500 ,
message: error . message || 'Failed to add item to cart'
})
}
} )
Server routes are automatically prefixed with /api. This route will be accessible at /api/cart/add.
Managing Cart State
Create a cart composable for managing cart state at composables/useCartState.js:
export const useCartState = () => {
const cart = useState ( 'cart' , () => ({
items: [],
itemCount: 0 ,
total: '0'
}))
const addItem = ( item ) => {
cart . value = {
... cart . value ,
items: [ ... cart . value . items , item ],
itemCount: cart . value . itemCount + item . quantity
}
}
const removeItem = ( itemKey ) => {
const newItems = cart . value . items . filter ( item => item . key !== itemKey )
const newCount = newItems . reduce (( sum , item ) => sum + item . quantity , 0 )
cart . value = {
... cart . value ,
items: newItems ,
itemCount: newCount
}
}
const updateQuantity = ( itemKey , quantity ) => {
const items = cart . value . items . map ( item =>
item . key === itemKey ? { ... item , quantity } : item
)
const itemCount = items . reduce (( sum , item ) => sum + item . quantity , 0 )
cart . value = {
... cart . value ,
items ,
itemCount
}
}
const clearCart = () => {
cart . value = {
items: [],
itemCount: 0 ,
total: '0'
}
}
return {
cart ,
addItem ,
removeItem ,
updateQuantity ,
clearCart
}
}
Use the cart state in your components:
< script setup >
const { cart } = useCartState ()
</ script >
< template >
< div >
< p > Cart items: {{ cart . itemCount }} </ p >
</ div >
</ template >
Nuxt makes it easy to manage SEO with the useSeoMeta composable:
< script setup >
useSeoMeta ({
title: 'My Headless Store' ,
description: 'Shop our amazing products powered by WooCommerce and CoCart' ,
ogTitle: 'My Headless Store' ,
ogDescription: 'Shop our amazing products powered by WooCommerce and CoCart' ,
ogImage: 'https://yourstore.com/og-image.jpg' ,
twitterCard: 'summary_large_image'
})
</ script >
Running Your Project
Start the development server:
Visit http://localhost:3000 to see your store.
Building for Production
Build your site for production:
Preview the production build:
Deployment Options
Nuxt can be deployed to various platforms:
Zero configuration deployment # Install Vercel CLI
npm i -g vercel
# Deploy
vercel
Nuxt automatically detects Vercel and configures itself appropriately.
Easy deployment with built-in features Create a netlify.toml file: [ build ]
command = "npm run build"
publish = ".output/public"
Connect your repository to Netlify and deploy.
Global edge network deployment
Connect your Git repository to Cloudflare Pages
Set build command: npm run build
Set build output directory: .output/public
Deploy
Nuxt automatically detects Cloudflare Pages and configures itself.
Deploy to any Node.js hosting After running npm run build, you can start the production server: node .output/server/index.mjs
Or use PM2 for process management: pm2 start .output/server/index.mjs --name "my-headless-store"
Generate a static site For fully static sites, you can use: This creates a .output/public directory that can be deployed to any static hosting service like GitHub Pages, AWS S3, or Nginx.
Version-Specific Features
Nuxt 3 Specific Features
Stable ecosystem : All major modules and libraries are fully compatible
Long-term support : Maintenance until January 2026
Production-ready : Battle-tested in thousands of applications
Extensive documentation : Comprehensive guides and community resources
Recommended Modules for Nuxt 3 # Image optimization
npm install -D @nuxt/image
# PWA support
npm install -D @vite-pwa/nuxt
# Icon support
npm install -D @nuxt/icon
Nuxt 4 Specific Features
Compatibility date : Use compatibilityDate in config for controlled updates
Performance improvements : Enhanced build times and smaller bundle sizes
New defaults : Better defaults for modern web development
Future-ready : Latest features and improvements from the Nuxt team
New in Nuxt 4
Shared app/ directory : New folder structure for better organization
Normalized useFetch and $fetch defaults : More consistent API behavior
Future Nitro 3 : Upcoming server engine improvements
Migration from Nuxt 3 to Nuxt 4 If you want to upgrade an existing Nuxt 3 project to Nuxt 4: # Update package.json
npm install nuxt@latest
# Add compatibility date to nuxt.config.ts
# compatibilityDate: '2024-11-01'
Troubleshooting
If you encounter CORS errors, you may need to configure WordPress to allow cross-origin requests. See CORS documentation . You can also add CORS headers in your Nuxt config: export default defineNuxtConfig ({
routeRules: {
'/api/**' : {
cors: true ,
headers: {
'Access-Control-Allow-Origin' : '*' ,
'Access-Control-Allow-Methods' : 'GET,HEAD,PUT,PATCH,POST,DELETE' ,
}
}
}
})
Verify your NUXT_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
Debug tip : Add logging to your composable:console . log ( 'API_BASE:' , API_BASE )
console . log ( 'Response:' , data . value )
Hydration Mismatch Errors
If you see hydration mismatch warnings:
Ensure data fetching is done with useAsyncData or useFetch
Check that your component structure matches between server and client
Avoid using browser-only APIs during SSR
Use <ClientOnly> for client-side only components:
< ClientOnly >
<BrowserOnlyComponent />
</ ClientOnly >
Module Compatibility (Nuxt 4)
Some Nuxt modules may not yet support Nuxt 4. Check the module’s documentation for compatibility. If a module isn’t compatible yet:
Check for updates or beta versions
Look for alternative modules
Consider staying on Nuxt 3 until the module is updated
Report the issue to the module maintainer
Next Steps
Now that your Nuxt project is set up with CoCart:
Build product listing pages with dynamic routes
Create a shopping cart component with real-time updates
Implement checkout functionality
Add user authentication with JWT
Optimize images with Nuxt Image module
Add PWA capabilities with @vite-pwa/nuxt
Resources