plugins/aem/edge-delivery-services-content-ops/skills/product-page-seo/SKILL.md
Optimize AEM Edge Delivery Services commerce product pages for search engine crawling and indexing. Audits client-side rendered product content for crawlability, validates meta tags, Product schema.org structured data, canonical URLs, and image optimization. Addresses the core challenge that EDS product pages render catalog data via JavaScript, which search crawlers may not fully execute. Use when product pages are not appearing in search results or have poor search visibility.
npx skillsauth add adobe/skills product-page-seoInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Audit AEM Edge Delivery Services commerce product pages for search engine crawlability, then remediate. EDS commerce PDPs are unusual: the initial HTML is a template with a product-details block but no catalog data. The block's JavaScript reads the product ID from the URL, queries the Catalog Service GraphQL API, and renders name, price, description, and images into the DOM.
The risk: crawlers may not execute that JavaScript, or may not wait for the API call. Googlebot renders JS with a delay and resource limits; Bing, social, and AI bots often skip it entirely. Anything not in the initial HTML response is not guaranteed to be indexed. The EDS query index also only covers authored document content, so route-based product data never enters it.
This skill fetches external web pages for analysis. When fetching:
Product pages are live but absent from search results, flagged "Discovered/Crawled - currently not indexed" in Search Console, lacking rich results, or losing rankings after migrating from a server-rendered platform. Also for SEO readiness checks before launch or after PDP block/catalog changes.
Not suited for non-commerce EDS pages (no client-side rendering issue), Adobe Commerce backend SEO, general content/keyword SEO, or initial storefront setup (use storefront-setup first).
storefront-setup (set up the PDP/PLP first), catalog-audit (validate catalog data accuracy), structured-data (non-commerce schema), sitemap-audit (verify product URLs are in the sitemap).
Sample at least 3 product pages (one simple, one configurable, one from another category). For each, compare what a non-JS crawler sees against what a browser renders.
Fetch the raw, non-executed HTML — this is the crawler's view:
curl -sL -A "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" \
"https://store.example.com/products/blue-jacket" -o raw.html
# Inspect what is present without JS:
grep -iE "<title>|name=\"description\"|rel=\"canonical\"|application/ld\+json|og:" raw.html
For each page, record whether each element is in the initial HTML or added by JS:
| Content Element | In Initial HTML? | Added by JS? | SEO Impact |
|----------------|:---------------:|:------------:|------------|
| Product name (H1) | ? | ? | Critical |
| Product price | ? | ? | Critical (rich results) |
| Product description | ? | ? | High |
| Product images | ? | ? | High |
| <title> | ? | ? | Critical |
| <meta name="description"> | ? | ? | High |
| og:title, og:image | ? | ? | Medium |
| JSON-LD structured data | ? | ? | High |
| Canonical URL | ? | ? | Critical |
If the initial HTML contains none of the product-specific content, that is a P0 crawlability risk.
In the raw HTML <head>, verify product-specific meta exists before JS runs:
{Product Name} | {Brand}, 50-60 chars. A generic template title that only updates after JS is P0.og:title, og:description, og:image (primary product image), og:type=product, og:url matching canonical. Missing og:image is P1.If meta is JS-only, recommend a strategy from references/product-page-seo-reference.md.
Check for <script type="application/ld+json"> with a Product type in the raw HTML. Absent = P0; present but JS-injected only = P1. Verify required properties are populated and that price/availability match the displayed values (mismatches violate Google policy = P0). See the property table in references/product-page-seo-reference.md.
Inject the schema in the PDP block (product-details.js) after product data loads, and ideally also server/edge-side for non-JS crawlers:
function injectProductSchema(product) {
const schema = {
'@context': 'https://schema.org',
'@type': 'Product',
name: product.name,
image: product.images?.map((i) => i.url),
description: product.description,
sku: product.sku,
brand: product.brand ? { '@type': 'Brand', name: product.brand } : undefined,
offers: {
'@type': 'Offer',
price: product.price.final,
priceCurrency: product.price.currency,
availability: product.inStock
? 'https://schema.org/InStock'
: 'https://schema.org/OutOfStock',
url: window.location.href,
},
};
Object.keys(schema).forEach((k) => schema[k] === undefined && delete schema[k]);
const el = document.createElement('script');
el.type = 'application/ld+json';
el.textContent = JSON.stringify(schema);
document.head.appendChild(el);
}
Recommend validating with Google's Rich Results Test after deployment.
In the raw HTML <head>, verify <link rel="canonical"> exists (missing = P0) and that it:
.aem.page/.aem.live preview domain (preview canonical = P0).?color=blue) to the base product URL unless variants are separate pages (P1).JS-set canonicals risk crawlers seeing the template canonical (P1) — set it server/edge-side or in head.html.
curl -sL "https://store.example.com/robots.txt"
Confirm product paths (e.g., /products/, /categories/) are not in a Disallow rule (P0 if blocked) and that a Sitemap: directive points to the production domain.
EDS does not auto-generate sitemaps for route-based product pages, so a custom generator is usually required. Build one from the Catalog Service:
async function generateProductSitemap(products, base) {
const urls = products
.map((p) => ` <url>\n <loc>${base}/products/${p.urlKey}</loc>\n` +
` <lastmod>${p.updatedAt.slice(0, 10)}</lastmod>\n </url>`)
.join('\n');
return `<?xml version="1.0" encoding="UTF-8"?>\n` +
`<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n${urls}\n</urlset>`;
}
Verify every product canonical appears in the sitemap (missing = P0), <lastmod> is current (P1 if stale), and that catalogs over 50,000 URLs use a sitemap index (P1 if exceeded).
Check product images against the table in references/product-page-seo-reference.md. The highest-impact issue is lazy-loading the primary (LCP) product image — it must use loading="eager" and fetchpriority="high" (P0). If alt text comes from the Catalog Service, verify quality at the API level since one bad value propagates to every page.
Based on Steps 1-6, recommend a tier (see references/product-page-seo-reference.md) chosen by catalog size, search-traffic importance, and technical capacity. Tier 2 (hybrid: pre-populated meta + edge-injected JSON-LD) is the recommended minimum; Tier 3 (pre-rendered critical content) is recommended for large catalogs.
| Check | Status | Priority | Details | |-------|--------|----------|---------| | Product content in initial HTML | Pass/Fail | P0 | % in initial HTML vs JS-rendered | | Title contains product name | Pass/Fail | P0 | Current title | | Meta description product-specific | Pass/Fail | P1 | Current description | | Product JSON-LD present + accurate | Pass/Fail | P0 | Properties, price/availability match | | Canonical URL correct | Pass/Fail | P0 | Current canonical | | Product URLs in sitemap | Pass/Fail | P0 | Count found | | Image alt text | Pass/Fail | P1 | Sample values | | LCP image loading priority | Pass/Fail | P0 | Loading attribute | | Robots.txt allows product crawling | Pass/Fail | P0 | Relevant directives |
Rate overall: High / Medium / Low.
For each: what to change (file + location), why it matters (search impact), how to implement (concrete code/config).
Recommend Tier 1, 2, or 3 with a specific implementation plan.
For implementation tables and troubleshooting, see references/product-page-seo-reference.md.
tools
Use the run-workflow MCP to discover, compose, execute, publish, and save Adobe Firefly workflows. TRIGGER when: user asks what actions are available, what the MCP can do, how to process images/video/3D via workflow, wants to build/run/save/publish a workflow, OR pastes any workflow/batch/execution ID. BARE ID (UUID/workflowId/batchId) = INSPECT ONLY — call inspect_run, NEVER run_workflow_submit. ALWAYS call list_actions first for capability/discovery questions. DO NOT TRIGGER for direct Firefly API calls without MCP (use firefly-api-specs).
tools
Run predefined featured workflows via run-workflow MCP. TRIGGER when user names a featured workflow (retargeting, banners at scale, localization, packaging, banner advertising, etc.) or asks to run a known marketing/production workflow. Requires run-workflow MCP. ALWAYS call get_featured_workflow before compose_workflow. DO NOT TRIGGER for custom one-off workflows with no named template — use run-workflow skill.
tools
Migrate an Adobe Commerce App Builder project from the Integration Starter Kit or Checkout Starter Kit to the new App Management approach. Run from the root of the App Builder project to be migrated. Pass --auto to skip confirmation prompts (suitable for CI or batch use) — auto mode prints a summary of all Q&A questions answered with their defaults. Pass --doc-scan-only to scan README.md and env.dist for outdated content without modifying any files. Use when the user wants to migrate an App Builder project from the Integration Starter Kit or Checkout Starter Kit to the App Management approach, or mentions upgrading their Adobe Commerce extension architecture.
development
Add or modify webhook interceptors in an Adobe Commerce app. Use when the user wants to intercept Commerce operations to validate input, append data, or modify behavior — before or after execution. Requires a base app initialized with commerce-app-init.