All posts
Ray Iyer
Ray Iyer
Co-founder, Anglera

The technical SEO checklist for commercetools product pages

A platform-specific technical SEO checklist for commercetools product pages: rendering, JSON-LD, meta tags, canonicals, images, and crawlability.

The technical SEO checklist for commercetools product pages

commercetools is headless: there is no built-in storefront, so every technical SEO decision — what gets rendered, what shows up in view-source, what search engines and AI agents actually receive — lives in whatever frontend you've paired with it (commercetools Frontend on Next.js, a custom Next.js/Nuxt build on the Composable Commerce API, or a legacy SPA). This checklist walks through the parts of a commercetools-backed PDP that most often break SEO, with the actual field names and mechanisms involved, so you can audit your own implementation against them.

Rendering: confirm the PDP is actually in the HTML

Product data in commercetools lives behind the Product Projections / GraphQL API, so nothing renders until your frontend fetches it and puts it into a response. commercetools Frontend handles this with a Next.js catch-all route (pages/[[...slug]].tsx in the Pages Router, or the equivalent App Router segment) where "all Data Sources are executed in parallel" server-side before the page reaches the client. On a custom storefront built directly on the API, the same principle applies regardless of framework: title, description, price, availability, and specs need to be present in the server-rendered or statically generated HTML, not injected client-side after hydration.

This matters more for AI agents than for classic search bots. Googlebot renders JavaScript, but many AI crawlers and answer-engine fetchers take the raw HTTP response and never execute it. If your PDP content only exists after a client-side fetch, those systems see an empty shell.

Structured data: map Product schema to commercetools fields

commercetools doesn't generate structured data for you — you build the JSON-LD from masterData.current on the Product and inject it server-side. A practical mapping:

{
  "@context": "https://schema.org/",
  "@type": "Product",
  "name": "product.masterData.current.name",
  "description": "product.masterData.current.description",
  "sku": "product.masterData.current.masterVariant.sku",
  "image": [
    "largest available size from masterVariant.images[].url"
  ],
  "offers": {
    "@type": "Offer",
    "priceCurrency": "from the selected price's currencyCode",
    "price": "from the selected price's centAmount / 10^fractionDigits",
    "availability": "derive from the variant's inventory/availability data",
    "url": "canonical product URL built from the localized slug"
  }
}

Two commercetools specifics to know before you build this: there's no native GTIN/MPN field on a Product — gtin13 or mpn need to be modeled as custom attributes on the Product Type and mapped explicitly. And price/availability aren't flat fields; they come from Price Scopes/Channels and inventory, so the JSON-LD should reflect whatever price and stock your storefront logic actually selects for the visitor, not just the first price in the array. Google's structured data guidelines for Product list which fields are required versus recommended for rich-result eligibility.

Titles and meta descriptions

ProductData (both current and staged) has three built-in, localized fields for exactly this purpose: metaTitle, metaDescription, and metaKeywords, documented as "used by search engines" and kept separate from the display name and description — so a merchandiser can write a customer-facing name ("Classic Leather Jacket") and a distinct title tag without touching the display copy. On commercetools Frontend, page folders carry their own seoTitle/seoDescription/seoKeywords configuration for non-product pages, accessible in data sources via context.pageFolder.configuration.seoTitle. Build a fallback chain (product metaTitle → product name → category name) so pages never ship an empty or generic title tag.

Canonical tags and duplicate PDP URLs

Slugs are commercetools' mechanism for clean URLs: a slug is the user-defined identifier used in a deep-link URL for a product, and per the API reference it "must be unique across a Project," though a single product can reuse the same slug value across its own different locales. In practice that means no two different products can ever share a slug string, regardless of locale — so slug collisions are a project-wide validation error, not a per-locale one. The duplicate-content risk isn't the slug itself — it's everything downstream: the same product reachable at multiple category-prefixed paths (a product can belong to more than one category), plus variant-selection and filter query parameters. Pick one canonical path per product per locale (typically a flat product-slug path such as /products/product-slug, or a single primary category path) and self-reference it with a canonical link tag, even when the PDP is also linked to from other category or search-filtered URLs.

Images and alt text

commercetools stores uploaded images on its own CDN and automatically generates multiple resolutions per upload — documented examples include a thumb size (90x60), a full/large size (600x400), and a zoom size (3000x2000) — each source carries its own key, dimensions, and content type, so reference the size key your layout needs rather than re-processing images yourself. The catch on alt text: the Image type's label field is a single plain string, not a LocalizedString, so it can't natively hold per-locale alt text the way name or slug can. For real, localized, descriptive alt text (not a repeated product name), use a separate mechanism — a custom attribute on the product type, or a mapping table in your frontend — populated per locale, per image.

Internal linking and breadcrumbs

Categories carry their own localized, unique slugs, and commercetools models category hierarchy through parent/child references, so breadcrumb trails (Home / Category / Subcategory / Product) can be built directly from the category tree rather than hardcoded. Render breadcrumbs as real anchor links (not JS-only navigation state) and add corresponding BreadcrumbList structured data. Cross-links between PDPs — accessories, "customers also viewed," variant switches — should also be real anchor tags pointing to the product's canonical slug URL, since crawlers and AI agents that follow links to map a catalog rely on discoverable anchors, not JS-driven pickers that only update state.

Performance

Server-rendered or statically generated PDPs with CDN-cached data sources give you the biggest Core Web Vitals win with the least custom work — cache product responses at the edge and revalidate on catalog updates rather than fetching on every request. Serve the CDN-generated image size that matches its rendered dimensions (don't ship the zoom asset in a product grid), and lazy-load below-the-fold images while keeping the primary hero image eager so it doesn't hurt LCP.

Crawlability: sitemaps and redirects

On commercetools Frontend, sitemap generation is a documented pattern: separate route handlers (e.g., src/app/[locale]/sitemap-products.xml/route.tsx, plus sitemap-categories.xml and sitemap-static.xml) page through products and categories with cursor-based pagination and emit SiteMapField entries with lastmod/changefreq, and a postbuild script runs after next build to assemble a master sitemap.xml index referencing every locale variant. Whatever frontend you use, the same shape applies: paginate the full catalog, include lastmod, and reference the sitemap index from robots.txt. When slugs change — a rebrand, a URL restructure, a product merge — commercetools Frontend's Studio supports bulk redirect upload via CSV (up to 500 rows, UTF-8, semicolon-delimited); on a custom stack, replicate this as a redirect map your edge middleware checks before falling through to a 404.

How to validate

  • View-source (not just the rendered DOM) on a live PDP and confirm title, meta description, canonical link, and the full JSON-LD block are present in the raw HTML response. A fast sanity check:
curl -s https://yoursite.com/products/product-slug | grep -i "application/ld+json"
  • Run the page through Google's Rich Results Test to confirm the Product markup is valid and eligible, and check for missing required fields.
  • Diff view-source against the rendered DOM in Chrome DevTools; if key content (price, title, description) only appears in the rendered DOM and not view-source, it's client-side only and invisible to non-JS-executing crawlers.
  • Fetch /sitemap.xml and a locale-specific sitemap directly and confirm product counts roughly match your live catalog size.
  • Spot-check a few slug-changed products against your redirect map to confirm old URLs 301 rather than 404.

Verified as of July 2026 against current commercetools API and Frontend documentation; confirm field names and menu paths against your own plan and Frontend version, since some page-folder schema and Studio features are configuration-dependent.

None of this replaces having good data to put on the page in the first place — a perfect JSON-LD template still needs an accurate GTIN, a real spec sheet, and descriptive alt text behind it. Anglera enriches that underlying product data continuously in your PIM or commerce platform, including the custom attributes commercetools doesn't natively provide (GTIN, MPN, per-locale alt text), so the rendering work above has something rich to actually display.

Ray Iyer

About the author

Ray IyerCo-founder, Anglera

Ray is a co-founder of Anglera, building the product-data infrastructure for agentic commerce — turning messy catalogs into structured, AI-readable data that buyers and answer engines can find. Previously product at Uber; Stanford CS.

See it on your own SKUs.

A 30-minute walkthrough on your categories and your supplier data.

Book a demo