OneShot

Product Feed & Merchant Center Compliance

Build Product Feed & Merchant Center Compliance on nextjs-feed

A Google Shopping product feed that survives the reviewer: spec-valid RSS 2.0 output validated offline against the transcribed product data spec, a feed-to-landing-page price and availability consistency crawler, GS1-correct GTIN handling with honest identifier_exists semantics, on-page structured data generated from the same source as the feed, and a diagnostic that reports each named disapproval cause — including the one it cannot check.

2 of 3 runs passedVerified on nextjs-feed: 2 of 3 runs passed on claude-sonnet-5.

The thing that gets your products disapproved is usually not in your feed. The #1 data-related disapproval cause is the feed disagreeing with the landing page Google crawls — a mismatch that is invisible to every validator that only looks at the file, and that no agent builds a check for, because it is not a property of the feed. This pack builds the whole surface: RSS 2.0 output validated offline against Google's product data spec shipped as versioned data, a consistency crawler that compares price, currency, sale window and availability against your real pages, GS1-correct GTIN handling, honest identifier_exists semantics, and page structured data generated from the same object as the feed so the two cannot drift. This pack is currently draft: no clean-room receipt has been published for it yet. When one is, this line will read N of M clean-room builds passed with all 15 acceptance checks — and until it does, treat everything below about the build as design intent, not evidence. What is already true today is that the acceptance suite runs hermetically: no Merchant Center account, no Google API, no network.

Example use cases

  • A Rails/Django/Next.js store with a custom storefront that has no platform-supplied Google channel — the merchant is hand-writing a feed template and finding out what is wrong from Merchant Center Diagnostics, one three-day review cycle at a time.
  • An agency running Shopping feeds for a dozen clients — the same feed-doctor command, wired into each client's CI, turns "the client emailed saying products vanished" into a build failure on the deploy that caused it.
  • A headless commerce build where prices live in one service and pages render in another — exactly the architecture where feed and landing page drift apart quietly, and exactly the one where a consistency crawler pays for itself in a week.
  • A handmade/vintage seller with no GTINs — custom goods, antiques and pre-1970 books are the case identifier_exists exists for, and the case everyone gets backwards in both directions.
  • A store adding a second target country — the conditional rules (apparel attributes, shipping, tax, currency) are country-scoped, and the pack makes running one pipeline per country a config change rather than a fork.

Scale envelope

Derived from the shipped architecture and the pinned stack, not from a load test. Nothing here is receipt-measured; the pack has no receipt yet, and where a number is an estimate it says so.

  • Feed generation is O(n) string building, in memory. At a typical ~700 B to 1.5 KB of XML per item, a 50,000-item feed is roughly 35–75 MB of string — fine for Node's default heap on a small container. First ceiling: about 100,000 items, where the whole-feed-in-memory approach starts to matter more than anything else in the pipeline. Estimate.
  • Upgrade path past that ceiling: stream the feed instead of building it (the builder is one function and the <item> loop is the only part that changes), or split by google_product_category into several data sources — Merchant Center accepts multiple. Google's own documented ceiling for a feed file is far higher (4 GB), so this is a Node ceiling, not a Google one.
  • The crawler is the real constraint, and it is time, not memory. At the default concurrency of 4 and a 200 ms page fetch, that is ~20 pages/second: 10,000 products in roughly 8–9 minutes, 100,000 in about 1.4 hours. Estimate — your page latency is the whole variable.
  • Upgrade path for the crawl: raise concurrency to whatever your own origin tolerates (it is a parameter, and you are crawling yourself, so the limit is your infrastructure and your WAF, not a third party's rate limit); or stop crawling everything — crawl the items that changed since the last run, plus a rotating sample. That is the change most catalogues over ~20,000 items should make on day one.
  • Validation is pure CPU over a JSON rule set. It is not the bottleneck at any catalogue size a Node process can hold.
  • Storage: none. The pack persists nothing between runs, which is why there is no database in the stack and why verify.sh needs no Docker.
  • One feed per target country is an architectural boundary, not a limitation to route around: the conditional rules and the currency check are country-scoped. Three countries is three configs through one pipeline.

What this comfortably covers as shipped: a single-country catalogue in the hundreds-to-tens-of-thousands of SKUs, with the doctor run in CI on every deploy and on a daily cron. What it isn't sized for out of the box: six-figure catalogues crawled in full every night, or multi-channel feed transformation across Google, Meta, Amazon and TikTok — the second one is what the feed-management SaaS category exists for, and this pack does not pretend otherwise.

On token savings: we are not publishing one. Every dollar figure in this catalogue that we stand behind comes from a harness receipt, and this pack does not have one yet. Buy it for the bug classes above — particularly the two that are invisible to any feed-only validator — not for a build-cost estimate we would have to invent.

What you get

Features:

  • buildFeed() — RSS 2.0 output with the exact http://base.google.com/ns/1.0 namespace, deterministic attribute order, full XML escaping, and absent optionals omitted rather than emitted empty
  • validateCatalog() — conformance to the shipped product data spec: required and conditional attributes, lengths, enums, price format, apparel and variant rules, retired and unknown attribute names
  • crawlCatalog() — bounded-concurrency landing-page crawl comparing feed to page: price amount, currency, effective sale price, availability, missing markup, JavaScript-only markup, unreachable page
  • productJsonLd() — schema.org Product/Offer markup generated from the same Offer as the feed, sale-window aware, server-rendered
  • checkGtin() — GS1 mod-10 across all four GTIN lengths, restricted and coupon prefixes, ISBN-10 detection, all rules read from the spec file
  • resolveIdentifiers() — the gtin / mpn / brand / identifier_exists decision, with the custom-product case and the misrepresentation case separated
  • diagnose() + formatReport() — findings grouped under named disapproval causes with source URLs, blocking vs. warning counts, and an explicit NOT CHECKED section
  • CatalogSource seam — Shopify, your own database or a CSV export is one class; a JsonCatalogSource ships
  • The product data spec as versioned data (spec/gmc-product-spec.json), with spec/REVISION.md and MIGRATION.md covering how to refresh it
  • Full vitest acceptance suite plus a local fixture storefront with planted price and stock drift — no Merchant Center account, no network, no Docker
  • One-page human go-live checklist covering the only manual steps

Screens/pages:

  • GET /feed.xml — the product feed, application/rss+xml
  • /products/[id] — reference landing page whose JSON-LD is rendered into the server HTML from the same Offer as the feed
  • CLI: npm run feed-doctor — validate + crawl + diagnostic report; exits 1 when a blocking finding exists, so it works as a CI gate
  • CLI: npm run feed-write — writes public/feed.xml

How it works

  1. 01

    Buy the pack

    Instant download: PRD, architecture decisions, task graph, acceptance tests, scaffold, and per-phase prompts.

  2. 02

    Feed it to your agent

    Claude Code or Cursor builds inside the pinned scaffold — no context needed beyond the pack itself.

  3. 03

    Verify

    Run the pack's acceptance script. It checks the same things our verification harness checked.

nextjs-feed versions lock

node24.18.1
next16.2.12
react19.2.8
react-dom19.2.8
typescript6.0.3
cheerio1.2.0
vitest4.1.10
@types/node24.13.3
@types/react19.2.18
@types/react-dom19.2.4

Prescribed services

Disclosure: some links on this page are affiliate links. We may earn a commission if you sign up through them, at no extra cost to you. We only link to services the pack actually verified against.