Retail & E-commerce// diagnostic

The page says in stock and the cart refuses: a rendering fault

In short

Query the commerce API for that variant, at that location, at the moment the badge is wrong. If the API says out of stock while the page says in stock, this is a render-path fault — the page is showing a value that was true earlier — and nothing in your inventory system needs touching. If the API is also wrong, you are in the wrong article and the fault is upstream in inventory.

Key takeaways

  • One API call separates a stale render from a wrong stock number. Make it before anyone opens the inventory system.
  • Availability is not a field. It is a question with a variant, a location and a moment in it, and a cache flattens all 3.
  • Give each surface a tier: collection tiles may be soft and hours old, the product page must be live, the cart is the only one allowed to promise.
  • A search index refreshed on a schedule is a cache with a nicer name, and it is where most wrong badges are actually rendered from.
  • Clients that never run your scripts — crawlers and shopping agents — see only what the server sent. A client-fetched badge does not exist for them.

The product page shows an in-stock badge, the shopper presses add to cart, and the cart refuses. The instinct is to open the inventory system, and in most of these reports that is the wrong system. Ask the commerce API for that variant's availability, at that location, at the moment the badge is wrong. If the API answers correctly, nothing about your stock data is broken — the page is displaying a value that was true at some earlier moment.

That single request splits the problem cleanly. A render-path fault is a caching and composition problem inside the storefront, fixable this week by the team that owns the front end. A wrong API answer is an inventory-truth problem, and it belongs to a different conversation entirely — starting with which system is allowed to say how much stock there is. Everything below assumes the API was right.

The one request that decides which fault you have

  1. Reproduce the wrong badge and keep the tab open. This is a time-sensitive fault and a reproduction 20 minutes later is a different measurement.
  2. Call the commerce API for the exact variant, passing the same market and location context the page was rendered with. Record the response and the time.
  3. Compare 3 answers: what the page shows, what the API returns, and what the cart says when you try to add the item.
  4. Read the page response headers. Cache status and age tell you whether you were served a stored copy and how old it was.
  5. Search the served HTML for the badge text. If it is in the source, the value was baked in at render time. If it is not, a client request produced it and the client is holding a stale answer.

Where staleness gets in on the way to the page

CauseSignatureCheck
The page was cached with availability inside itEvery visitor sees the same wrong badge until the cache expires or is purgedBadge text present in the served HTML, and a non-zero cache age on the response
The client store is holding an earlier answerWrong only for shoppers who have been browsing a while; a hard reload fixes itBadge absent from the HTML source; the value came from an in-page query cache
A collection or search index rendered the tileThe listing page and the product page disagree with each otherCompare the tile against the product page in the same session
Availability was resolved without a locationWrong only for some markets, stores or delivery addresses, and consistently soRe-query with an explicit location and see whether the answer changes
Four render-path causes, and the check that separates them

The fourth row is the one that gets rebuilt twice, because it does not look like a caching bug. The page asked whether the item is available and the system answered for a default — a total across all locations, or the primary warehouse — while the shopper is being served by a market that cannot fulfil it. That is the same class of conflict between network location, stored preference and URL that produces shoppers landing on the wrong country's storefront, and it needs the same fix: resolve the context once, deliberately, before anything reads from it.

Availability is not a field on a product. It is a question with a variant, a location and a moment in it, and a cache flattens all three.

Availability is a question, not a property

Most of these bugs come from modelling availability as a boolean that hangs off a product. It is not. The honest form is a function of variant, location or fulfilment method, quantity requested, and time — and any storefront that renders it as a single stored flag has already discarded 3 of those 4 inputs before the caching layer gets involved.

  • Ask per variant, never per product. A product-level badge on a page where 6 of 8 sizes are gone is wrong for most shoppers who read it, and it is the reason a picker needs the precomputed availability lattice described in a variant picker that never offers a dead end.
  • Ask for a quantity. 'Available' and 'available in the 3 the shopper wants' are different answers, and the cart will discover the difference whether or not the page did.
  • Ask with a fulfilment method. Deliverable from a warehouse and collectable from a named store are separate questions with separate answers.
  • Carry the answer's age. If a badge is rendered from a value fetched 4 minutes ago, the surface that renders it should know that, because it decides what language is honest.

Three tiers of claim, and the words each is allowed

Not every surface needs a live answer, and pretending otherwise makes every page slow. Assign each surface a tier, a maximum staleness, and the language it may use at that staleness.

SurfaceMaximum stalenessLanguage it may use
Collection tiles and search resultsHours, matching the index refreshSoft only — 'usually available', or nothing at all
Product pageSeconds to a minute, fetched outside the cached shellSpecific, including low-stock and lead-time statements
Add to cart and checkoutNone. Revalidated at the moment of the actionBinding. This is the surface that commits
What each surface may claim

There is a real cost to the live fetch, which is why the tiering matters. An availability request that fires on every product view is one more thing in the page's critical path, and it has to be allocated for rather than added — the exercise in the performance budget a product page is built against. Fetching it in parallel with the shell rather than after it usually costs nothing visible.

Collection pages are the hardest surface

A listing page shows 48 tiles, and asking a live availability question for each one is not sensible. So the tiles are rendered from a search index, and a search index is a cache with a nicer name: it is refreshed on a schedule, often overnight, and it will happily show yesterday's availability all day.

Two answers work and one does not. The one that does not is asserting a hard in-stock claim on a tile. The 2 that work are dropping the claim entirely from tiles, or making that one field event-driven — the index takes a targeted update when an item sells out, separate from the full rebuild. Ranking behaviour is a related but distinct decision, covered in how out-of-stock products should behave in search.

The reader that is not a browser

Once availability is fetched by a script rather than rendered on the server, it stops existing for every client that does not run scripts: crawlers, feed consumers and shopping agents. They see the shell. If the shell says nothing, they infer nothing; if the shell carries a stale server-rendered value, they will repeat it confidently on a surface you do not control.

That matters more than it used to, because an agent that finds your declared capabilities do not match what you can honour will simply stop transacting with you — the disqualification pattern in agents fetching your manifest and still skipping your store. Serving a machine-readable availability value on the server, refreshed on the same short lifetime as the badge, is the cheapest way to stay consistent across surfaces you can see and surfaces you cannot.

The same rule applies to an assistant of your own. If it answers from an index rebuilt overnight, it is another cached surface with a friendly voice, and it needs a live authoritative call before it commits to anything — the grounding pattern behind AI agents and automation work. Where that assistant and its retrieval layer actually run, and what that costs in latency and control, is a separate decision set out in private LLM deployment.

One boundary, stated plainly: none of this addresses 2 shoppers racing for the last unit, reservation windows, or a safety buffer that stops a shared pool overselling. Those are inventory-truth problems rather than rendering problems. What this page fixes is the far more common case where the number was right all along and the page was showing an old one — a storefront architecture fault inside the wider retail and e-commerce stack.

Frequently asked questions

Short answers to the follow-ups this page tends to raise.

How do I know whether this is a caching bug or an inventory bug?

Query the commerce API for that variant, at that location, while the badge is still wrong. If the API returns the correct availability, the fault is in how the page was rendered or cached and the inventory system is fine. If the API returns the same wrong answer as the page, the storefront is faithfully showing you a bad number and the problem is upstream, in whichever system is supposed to own the stock position.

What is a safe cache lifetime for an availability badge?

Shorter than the time it takes your fastest-selling item to sell out, which for most catalogues means seconds rather than minutes. Rather than tuning a number, take availability out of the cached document entirely: cache the page shell for as long as you like and fetch the badge separately with its own lifetime. A field that cannot be purged the moment it changes should not be making a hard claim.

Should collection pages show stock status at all?

Only softly, unless that one field is updated by event rather than by schedule. A tile rendered from a search index carries the index's refresh lag, so a hard in-stock claim there is a promise made on hours-old data. Either omit the claim on tiles and make it on the product page, or push targeted updates into the index when an item sells out.

The badge is right and the cart still refuses. What now?

Check what the cart is validating that the badge did not. The usual answers are quantity, location and fulfilment method: the page asked whether the item exists somewhere, and the cart asked whether 3 of them can be shipped to this address today. Align the questions — same variant, same quantity, same location — and the disagreement usually disappears without any caching change.

  • availability
  • caching
  • storefront architecture
  • diagnostics
// shipped work

The work behind this page

Builds from our portfolio that this page draws on.

Read next

Working on something in this space?

Tell us where you are in a sentence or two. We'll tell you honestly whether we're the right team, and what a sensible first slice of the work looks like.

Start the conversation