The biggest concern about headless commerce is SEO. If your storefront is a JavaScript application, will Google index it properly? The short answer: yes, if you implement SSR/SSG correctly. The long answer involves meta tags, structured data, sitemaps, and a few headless-specific challenges.

The Problem: Client-Side Rendering

A purely client-side rendered (CSR) React app sends an empty HTML shell to the browser (and to Googlebot):



  

Google can render JavaScript, but it’s slower, less reliable, and uses a separate rendering queue. Critical ecommerce pages — products, categories, landing pages — should never depend on client-side rendering for their indexable content.

The Solution: SSR and SSG

Next.js and Nuxt provide server-side rendering (SSR) and static site generation (SSG) out of the box. With these, your product pages are delivered as fully-rendered HTML:



  Blue Widget - YourStore
  
  


  

Blue Widget

$29.99

Premium blue widget with stainless steel construction...

Next.js: ISR (Incremental Static Regeneration)

The ideal approach for WooCommerce product pages:

// app/products/[slug]/page.js

export async function generateStaticParams() {

const products = await fetchAllProductSlugs();

return products.map(p => ({ slug: p.slug }));

}

export async function generateMetadata({ params }) {

const product = await fetchProduct(params.slug);

return {

title: ${product.name} | YourStore,

description: product.short_description,

openGraph: {

title: product.name,

description: product.short_description,

images: [{ url: product.images[0]?.src }],

type: 'website'

}

};

}

export default async function ProductPage({ params }) {

const product = await fetchProduct(params.slug);

return ;

}

// Revalidate every 5 minutes

export const revalidate = 300;

Pages are pre-rendered at build time and regenerated in the background when stale. Googlebot always gets fully-rendered HTML.

Meta Tag Management

In traditional WooCommerce, Yoast or RankMath handles meta tags. In headless, you manage them in your frontend framework.

Dynamic Meta Tags (Next.js App Router)

// lib/seo.js

export function generateProductMeta(product) {

const title = product.yoast_head_json?.title || ${product.name} | YourStore;

const description = product.yoast_head_json?.og_description ||

stripHtml(product.short_description).substring(0, 160);

return {

title,

description,

openGraph: {

title: product.yoast_head_json?.og_title || product.name,

description,

url: https://yourstore.com/products/${product.slug},

images: product.images.map(img => ({

url: img.src,

width: 800,

height: 800,

alt: img.alt || product.name

})),

type: 'website',

siteName: 'YourStore'

},

twitter: {

card: 'summary_large_image',

title: product.name,

description,

images: [product.images[0]?.src]

},

alternates: {

canonical: https://yourstore.com/products/${product.slug}

}

};

}

Leveraging Yoast Data

If Yoast SEO is installed on your WordPress backend, it exposes SEO data via the REST API:

// Fetch product with Yoast SEO data

const product = await fetch(

${WP_URL}/wp-json/wp/v2/product/${id}?_fields=yoast_head_json

);

const yoast = product.yoast_head_json;

// Use Yoast's pre-computed meta

const meta = {

title: yoast.title,

description: yoast.og_description,

canonical: yoast.canonical,

robots: yoast.robots

};

Structured Data (Schema.org)

Product schema is critical for rich snippets in search results. Generate it server-side:

function generateProductSchema(product) {

const schema = {

'@context': 'https://schema.org',

'@type': 'Product',

name: product.name,

description: stripHtml(product.short_description),

image: product.images.map(img => img.src),

sku: product.sku,

brand: {

'@type': 'Brand',

name: product.brands?.[0]?.name || 'YourStore'

},

offers: {

'@type': 'Offer',

url: https://yourstore.com/products/${product.slug},

priceCurrency: 'USD',

price: product.price,

availability: product.stock_status === 'instock'

? 'https://schema.org/InStock'

: 'https://schema.org/OutOfStock',

seller: {

'@type': 'Organization',

name: 'YourStore'

}

}

};

// Add reviews if available

if (product.rating_count > 0) {

schema.aggregateRating = {

'@type': 'AggregateRating',

ratingValue: product.average_rating,

reviewCount: product.rating_count

};

}

return schema;

}

// In your page component

Close Search Window