← 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-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
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.