← 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.
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-doctorcommand, 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_existsexists 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 bygoogle_product_categoryinto 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
concurrencyto 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.shneeds 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 exacthttp://base.google.com/ns/1.0namespace, deterministic attribute order, full XML escaping, and absent optionals omitted rather than emitted emptyvalidateCatalog()— conformance to the shipped product data spec: required and conditional attributes, lengths, enums, price format, apparel and variant rules, retired and unknown attribute namescrawlCatalog()— bounded-concurrency landing-page crawl comparing feed to page: price amount, currency, effective sale price, availability, missing markup, JavaScript-only markup, unreachable pageproductJsonLd()— schema.org Product/Offer markup generated from the sameOfferas the feed, sale-window aware, server-renderedcheckGtin()— GS1 mod-10 across all four GTIN lengths, restricted and coupon prefixes, ISBN-10 detection, all rules read from the spec fileresolveIdentifiers()— the gtin / mpn / brand /identifier_existsdecision, with the custom-product case and the misrepresentation case separateddiagnose()+formatReport()— findings grouped under named disapproval causes with source URLs, blocking vs. warning counts, and an explicitNOT CHECKEDsectionCatalogSourceseam — Shopify, your own database or a CSV export is one class; aJsonCatalogSourceships- The product data spec as versioned data (
spec/gmc-product-spec.json), withspec/REVISION.mdandMIGRATION.mdcovering 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 sameOfferas 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— writespublic/feed.xml
How it works
01
Buy the pack
Instant download: PRD, architecture decisions, task graph, acceptance tests, scaffold, and per-phase prompts.
02
Feed it to your agent
Claude Code or Cursor builds inside the pinned scaffold — no context needed beyond the pack itself.
03
Verify
Run the pack's acceptance script. It checks the same things our verification harness checked.
nextjs-feed versions lock
| node | 24.18.1 |
| next | 16.2.12 |
| react | 19.2.8 |
| react-dom | 19.2.8 |
| typescript | 6.0.3 |
| cheerio | 1.2.0 |
| vitest | 4.1.10 |
| @types/node | 24.13.3 |
| @types/react | 19.2.18 |
| @types/react-dom | 19.2.4 |
Prescribed services
- Railway — Railway referral credits
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.