Indexing headless commerce sites: Saleor, Medusa, Vendure, and friends
Headless commerce — Saleor, Medusa, Vendure, Commerce.js, Swell — moves the catalog into an API and lets you build the storefront in whatever frontend stack you want (Next.js, Astro, Remix). It's a great architectural choice. It's a terrible default for indexing, because the legacy SEO plugins that ship with WooCommerce, Shopify, and Magento don't exist in this world.
What breaks by default
- Sitemap generation. Headless backends rarely ship a /sitemap.xml endpoint — you build it yourself in the frontend.
- Canonical URLs. Multiple frontends (web, app, embedded) can point at the same backend product. Without explicit canonicals, Google sees duplicate content.
- Indexing on publish. Adding a product in the headless admin doesn't ping any indexing service — the frontend doesn't know the catalog changed.
- Structured data. No Product schema unless you wire it up in the frontend.
- Stale URL coverage. Deleted SKUs leave 404s; renamed slugs orphan old URLs.
The minimum viable indexing layer
- Sitemap generator in the frontend. For Next.js, an /app/sitemap.ts that fetches the catalog from the headless API at build (or per-request, cached).
- Webhook from headless admin to your frontend. Saleor / Medusa / Vendure all support outbound webhooks on product create/update/delete.
- Webhook handler that triggers either a frontend rebuild (for static / ISR sites) or an indexing API push (for dynamic sites).
- Product JSON-LD in the product page template — name, image, description, sku, brand, offers.price, offers.priceCurrency, offers.availability.
- Canonical tag = the canonical storefront URL. Always.
Webhook → IndexerNow handoff
The cleanest production setup: your headless admin fires a webhook on product change. Your frontend rebuilds the page (or re-renders on-demand). Then the webhook handler also calls your indexing pipeline. IndexerNow's webhook source feature gives you an endpoint that accepts a URL and pushes it through your OAuth-authenticated Indexing API connection.
// Saleor webhook handler (Next.js API route)
export async function POST(req) {
const event = await req.json();
if (event.event_type === "PRODUCT_UPDATED") {
const slug = event.product.slug;
const url = `https://shop.example.com/products/${slug}`;
// 1. Trigger frontend rebuild
await fetch(`https://api.vercel.com/v1/integrations/deploy/${process.env.VERCEL_HOOK}`);
// 2. Push to your indexing pipeline
await fetch(`https://indexernow.com/api/wh/${process.env.INDEXERNOW_WEBHOOK_TOKEN}`, {
method: "POST",
body: JSON.stringify({ url }),
});
}
return new Response("ok");
}Both expose separate PRODUCT_CREATED, PRODUCT_UPDATED, PRODUCT_DELETED events. Subscribe to the first two for indexing pushes. For deletes, push the URL too — Google needs to re-check and confirm the 410.
Catalog migrations: doing this without dropping rankings
Moving from WooCommerce to Medusa, or from Shopify to a headless Next.js storefront, almost always involves URL changes. Every URL change is a potential ranking loss. The checklist:
- Export every old product URL with its title, traffic, and current rank.
- Map every old URL to a new one. If a new URL doesn't exist, decide: redirect to a category or 410-delete?
- Implement 301 redirects in the new stack. Test 50 random URLs before flipping DNS.
- Submit the new sitemap to Search Console.
- Bulk-push the new URLs through the Indexing API to accelerate recrawl.
- After 7-14 days, re-inspect every old URL. They should show "Redirect" + the new URL indexed.
The seasonal launch playbook (Black Friday, etc)
Headless commerce shines when you can stand up landing pages fast. The indexing flow:
- Publish the seasonal collection 14 days before the campaign starts. Yes, hidden behind login or noindex — just get the URL structure ready.
- Day 7: remove noindex, push the URLs through the Indexing API.
- Day 4-1: monitor index coverage; re-push any URLs still in "Discovered, not indexed."
- Campaign day: all URLs already in the index = no warm-up loss.
Wire your headless commerce backend to IndexerNow — webhook a URL and we'll push it through your Google Indexing API quota.
Try IndexerNow free