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
Next.js is a powerful React framework that enables you to build fast, production-ready web applications. It’s an excellent choice for headless storefronts with CoCart because of its server-side rendering, API routes, excellent performance, and built-in optimizations.
This guide will walk you through setting up a Next.js project configured to work with CoCart API.
Why Next.js for Headless Commerce?
- Hybrid rendering - Use SSR, SSG, or ISR based on your needs
- Built-in API routes - Create backend endpoints without a separate server
- Optimized performance - Automatic code splitting and image optimization
- SEO friendly - Server-side rendering for better search engine visibility
- Great DX - Fast refresh, TypeScript support, and comprehensive documentation
- Vercel deployment - Zero-config deployment with built-in CI/CD
Prerequisites
- Node.js 18.17 or higher
- A WordPress site with WooCommerce installed
- CoCart plugin installed and activated
- Basic knowledge of React and JavaScript
Creating a New Next.js Project
Create a new Next.js project using the official CLI:
npx create-next-app@latest my-headless-store
When prompted, choose the following options:
- Would you like to use TypeScript? → Yes (recommended) or No
- Would you like to use ESLint? → Yes (recommended)
- Would you like to use Tailwind CSS? → Yes (recommended)
- Would you like your code inside a
src/ directory? → Yes (recommended)
- Would you like to use App Router? → Yes (recommended)
- Would you like to use Turbopack for
next dev? → Yes (optional, for faster dev)
- Would you like to customize the import alias? → No (unless you have preference)
Navigate to your project:
Project Structure
Your Next.js project will have this structure:
my-headless-store/
├── src/
│ ├── app/ # App Router pages and layouts
│ │ ├── layout.tsx # Root layout
│ │ ├── page.tsx # Home page
│ │ └── api/ # API routes
│ ├── components/ # Reusable React components
│ ├── lib/ # Utility functions and API clients
│ └── styles/ # Global styles
├── public/ # Static assets
├── next.config.js # Next.js configuration
├── tailwind.config.ts # Tailwind configuration
├── package.json
└── tsconfig.json # TypeScript config (if using TS)
Create the necessary folders:
mkdir -p src/components src/lib
Environment Configuration
Create a .env.local file in your project root:
NEXT_PUBLIC_STORE_URL=https://yourstore.com
Variables prefixed with NEXT_PUBLIC_ are exposed to the browser. Server-only variables should not have this prefix.
Add .env.local to your .gitignore (it should already be there by default):
# .gitignore already includes
.env*.local
Create a .env.example for your team:
# .env.example
NEXT_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 (or .js if not using TypeScript):
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 = process.env.NEXT_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),
...params
});
try {
const response = await fetch(`${API_BASE}/products?${queryParams}`, {
next: { revalidate: 3600 } // Cache for 1 hour
});
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}`, {
next: { revalidate: 3600 } // Cache for 1 hour
});
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,
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
}),
cache: 'no-store'
});
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, {
cache: 'no-store'
});
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 }),
cache: 'no-store'
});
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',
cache: 'no-store'
});
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 API Routes
Next.js API routes allow you to create backend endpoints. Create an API route for cart operations.
Create src/app/api/cart/add/route.ts:
import { NextRequest, NextResponse } from 'next/server';
import { addToCart } from '@/lib/cocart';
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const { id, quantity = 1, ...options } = body;
const result = await addToCart(id, quantity, options);
return NextResponse.json(result, { status: 200 });
} catch (error: any) {
return NextResponse.json(
{ error: error.message },
{ status: 500 }
);
}
}
Updating the Root Layout
Update your root layout at src/app/layout.tsx:
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "My Headless Store",
description: "Your headless WooCommerce store powered by CoCart",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={inter.className}>
{children}
</body>
</html>
);
}
Testing Your Setup
Update the homepage at src/app/page.tsx:
import { getProducts } from '@/lib/cocart';
export default async function Home() {
const products = await getProducts({ per_page: 3 });
return (
<main className="container mx-auto px-4 py-12">
<h1 className="text-3xl font-bold text-zinc-900 dark:text-white mb-8">
Welcome to Your Headless Store
</h1>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
{products.map((product: any) => (
<div key={product.id} className="border rounded-lg p-4">
<h2 className="font-semibold">{product.name}</h2>
<p className="text-zinc-600">
{product.prices.currency_symbol}{product.prices.price}
</p>
</div>
))}
</div>
</main>
);
}
Running Your Project
Start the development server:
Visit http://localhost:3000 to see your store.
Building for Production
Build your site for production:
Start the production server locally:
Caching Strategies
Next.js offers several caching strategies for optimal performance:
Static Generation (SSG)
For pages that can be pre-rendered at build time:
export const revalidate = 3600; // Revalidate every hour
export default async function ProductsPage() {
const products = await getProducts();
// ...
}
Incremental Static Regeneration (ISR)
For pages that need periodic updates:
export async function generateStaticParams() {
const products = await getProducts({ per_page: 100 });
return products.map((product: any) => ({ id: String(product.id) }));
}
export default async function ProductPage({ params }: { params: { id: string } }) {
const product = await getProduct(params.id);
// ...
}
Server-Side Rendering (SSR)
For dynamic pages that need fresh data:
export const dynamic = 'force-dynamic';
export default async function CartPage() {
const cart = await getCart();
// ...
}
Deployment Options
Next.js sites can be deployed to various platforms:
- Vercel - Zero configuration deployment (recommended)
- Netlify - Easy deployment with built-in features
- AWS Amplify - Full-stack deployment
- Docker - Containerized deployment
- Your own server - Node.js server required
Deploying to Vercel
- Push your code to GitHub, GitLab, or Bitbucket
- Import your project on Vercel
- Add your environment variables
- Deploy!
Next Steps
Now that your Next.js project is set up with CoCart:
- Add shopping cart functionality with React Context or Zustand
- Implement checkout flow
- Add user authentication with JWT
- Optimize images with Next.js Image component
- Add loading states and error boundaries
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
NEXT_PUBLIC_STORE_URL is correct in .env.local
- Ensure CoCart is installed and activated
- Check that WooCommerce is configured properly
- Test API endpoints directly in your browser or Postman
Hydration Errors
If you see hydration mismatches:
- Ensure you’re not using browser-only APIs during SSR
- Use
'use client' directive for client-only components
- Check that your data is consistent between server and client renders
Resources