# Recently Purchased / Buy Again — Reasoning & Recommendations

**Companion to:** [feature-design-guidelines.md](feature-design-guidelines.md), [account-concepts.md](account-concepts.md), [saved-cart-concepts.md](saved-cart-concepts.md), [search-experience-concepts.md](search-experience-concepts.md), [USAClean_IA_Search_Roadmap.md](USAClean_IA_Search_Roadmap.md)
**Live preview:** https://demo.thecxlabs.com/v2/account-buy-again.html (account Buy Again) · https://demo.thecxlabs.com/v2/index-signed-in.html (based-on-purchases rail) · https://demo.thecxlabs.com/v2/plp.html · https://demo.thecxlabs.com/v2/pdp.html · https://demo.thecxlabs.com/v2/cart.html
**Date:** June 2026

This document explains how to turn USA Clean's **purchase history** into a personalization layer — both a dedicated **"Recently Purchased / Buy Again"** experience inside the account, and **"based on recent purchases"** modules seeded across the storefront. The thesis is small and load-bearing: **one deduped list of what an account actually buys is the cheapest, highest-trust personalization signal a B2B parts site has** — and almost every retention feature on the roadmap reads off it.

---

## TL;DR

USA Clean's biggest unmet lever is **returning-buyer retention**. The [IA roadmap](USAClean_IA_Search_Roadmap.md) found **39% of sessions are returning visitors, but only ~1.61% include a login** — thousands of repeat buyers re-deriving "what do I order for my Tennant T5" from scratch every visit. B2B parts buying is overwhelmingly **repeat consumable purchasing** (filters, brushes, squeegees, pads, bags on a cadence), which is exactly the shape that purchase history personalizes well.

Two distinct surfaces, often conflated, must stay separate:

- **Buy Again** — re-buy the items *you actually purchased*. A deduped, line-item view of your own order history. High trust, zero "creepiness," works for **any account with one past order**. This is the floor and ships in Stencil.
- **Based on recent purchases** — *recommendations inferred from* that history (complementary parts, "complete your last order," the consumable you're due to reorder). This is the merchandised, cross-page layer. Native BC can't do it; a custom build (P2) or Hawk Recommendations (already deployed, P3) can.

**What showing this unlocks** is the real argument. A single purchase-history list is the seed for: **auto-ship / subscriptions**, the **reorder pad**, **replenishment reminders** ("you're due for HEPA filters"), **one-click saved lists**, **spend/budget visibility**, **frequently-bought-together** recommendations, and **faster repeat checkout**. None of those need new data — they need this list surfaced.

| Phase | What ships | Effort |
|---|---|:---:|
| **Phase 1** _(Surface what we already have)_ | **Buy Again** view in the account (deduped line-items across orders, reorder + qty + save-to-list); native BC **Reorder**; native **recently-viewed** for guests + a **sign-in bridge**. Generalize the account framing beyond National Accounts. | **Medium** — Caster front-end on Storefront order data |
| **Phase 2** _(Personalize the storefront)_ | **"Based on recent purchases"** modules on home / PLP / PDP / cart; line-item **aggregation + cadence** logic ("bought 4× · ~every 6 wks · due now"); seed saved-lists and replenishment reminders from it. | Higher — custom aggregation service + 4 surfaces |
| **Phase 3** _(Predict & rank)_ | **Predictive replenishment** + auto-ship, **fleet-aware ranking** ("parts your fleet has bought for this model first"), FBT via **Hawk Recommendations** (already paid for). | Highest — **Catalyst-replatform candidate** |

---

## Current state

Today, purchase history exists but is **locked inside individual orders**:

- The only reorder affordance is a per-order **"Reorder"** link on each row of the orders list ([v2/account-orders.html](../v2/account-orders.html)). To re-buy one filter you bought last month you must remember which order it was in, open it, and reorder the whole thing.
- **No aggregated "things you buy" view.** Nothing dedupes line-items across orders into "here are the 23 SKUs this account actually purchases, ranked by how often."
- **No purchase-derived recommendations anywhere.** PLP, PDP, home, and cart are identical for a 5-year customer and a first-time guest. The signed-in home has a *frequency*-based "Reorder your usuals" rail ([v2/index-signed-in.html:1165](../v2/index-signed-in.html)) — a good start, but it is **re-buy**, not the *recommendation* layer, and it lives only on the home page.
- **Recently-viewed is cookie-only.** BC tracks viewed products in a cookie, but it is anonymous and resets — it never becomes "what *this account* looked at," and it is never positioned as the carrot to log in.
- **The account framing assumes a National Account.** [v2/account.html](../v2/account.html) leads with "Type: National," 3 sites, 5 users, fleet, approvals. A single-location buyer with one machine and one past order — the majority of the 39% — sees none of themselves in it.

**The data is already there. It just isn't surfaced, deduped, or generalized.**

---

## References

### Amazon Business — "Buy Again"
The category-defining pattern: a dedicated tab that dedupes everything you've ordered into a re-buyable grid, with "bought N times," reorder, and "add to list." The model to emulate for the account view — purchase history as a *shopping surface*, not an audit log.

### Grainger — order history + reorder lists
Industrial-distribution benchmark. Reorder from any past order, persistent reorder lists, and a "frequently ordered" view scoped to the account. Confirms the B2B expectation: repeat buyers want their catalog narrowed to *their* parts.

### Staples Business / Uline — replenishment
Consumable-cadence done well: "you usually reorder this every ~6 weeks," subscribe-and-save, and proactive "running low?" nudges derived from purchase intervals. The bridge from Buy Again (P1) to predictive replenishment (P3).

### McMaster-Carr — order history as navigation
For repeat industrial buyers, order history *is* the navigation — fast, dense, re-orderable rows. Sets the bar for the account Buy Again list's information density.

### Sweepscrub (cleaning-vertical peer)
Same catalog shape as USA Clean (parts by brand→machine), but no account-scoped reorder or purchase personalization — the exact gap this brief closes for the vertical.

---

## What BC Stencil gives us

- **Native Reorder + order history.** The stock account area (`templates/pages/account/orders.html` / `order.html`) lists past orders and exposes a native **"Reorder"** action that re-adds an order's items to the cart. This is the Phase-1 *floor* — no custom code to let a buyer repeat an order.
- **Storefront GraphQL `customer.orders` → `lineItems`.** The raw material for deduping: query the customer's orders and their line items, then aggregate client- or service-side into the "things you buy" list (SKU, name, qty, count of orders, last-ordered date). REST **Orders v2** (`/v2/orders`, `/products`) is the equivalent server-side path.
- **B2B Edition: company orders + Shopping Lists.** For B2B-Edition companies, order history spans the whole company (all buyers/sites), and native **Shopping Lists** give a persistence target for "save these as a list" — the durable home for a curated Buy Again set (shared with [saved-cart-concepts](saved-cart-concepts.md) Phase 2).
- **Native recently-viewed.** Stencil exposes recently-viewed products (cookie-based `recently_viewed`) renderable in any theme template — the honest, no-login basis for the guest surface.
- **Product `compatible_models[]` custom field.** The same field powering [model-landing](model-landing-concepts.md) and [search](search-experience-concepts.md) lets the PDP "you bought a compatible part" callout and fleet-aware ranking intersect purchase history with machine fit.
- **Storefront Cart API** for the add / add-all / qty actions every module needs.

**Sources:** [BC — Reordering products](https://support.bigcommerce.com/s/article/Reordering-Products), [BC Storefront GraphQL — Customer & Orders](https://developer.bigcommerce.com/docs/storefront/graphql), [BC — Shopping Lists (B2B Edition)](https://support.bigcommerce.com/s/article/Shopping-Lists), [BC Stencil — recently viewed](https://developer.bigcommerce.com/docs/storefront/stencil/themes/context/handlebars-reference).

---

## Stencil limitations

- **No cross-page "based on recent purchases" module.** Native BC has no merchandised recommendation surface driven by *this customer's* purchase history. The stock "Related products" are catalog-relationship based, not personalized. The home/PLP/PDP/cart modules are custom.
- **No line-item dedupe / aggregation across orders.** "Reorder" works *per order*; nothing rolls all orders up into a deduped, frequency-ranked SKU list. That aggregation is a custom query + transform.
- **No consumable-cadence logic.** "Bought every ~6 weeks · due now" requires computing inter-purchase intervals — no native field or report produces it.
- **No merchandised Buy-Again rail.** Surfacing reorder candidates outside the account (a home rail, a cart nudge) is theme + data work; BC ships no such component.
- **Recently-viewed is cookie-scoped, not account-scoped.** It knows what a browser *viewed*, never what an account *bought*, and doesn't survive a device switch. Honest copy must never imply we know a signed-out visitor's purchases.
- **No fleet-aware ranking.** Ordering reorder candidates by "what your registered machines consume" needs the fleet ↔ `compatible_models[]` ↔ purchase-history join — custom, and best on a headless data layer.

---

## App marketplace shortlist

| Option | What it is | Verdict |
|---|---|:---:|
| **Native BC Reorder + order history** | Stock per-order reorder action. | ✅ **Phase-1 floor.** Free, already present. Use it as the baseline; the custom Buy Again view *aggregates over* it, doesn't replace it. |
| **Custom purchase-history aggregation** (Storefront/REST orders → deduped list → Caster UI) | A one-time build: query orders, dedupe line-items, rank by frequency/recency, render in Caster. | ✅ **Phase 1–2 path.** Narrow surface, Caster-native, **we own the data** (Rule #3). No subscription competes — it reads data we already have. |
| **Hawk Recommendations** (already deployed) | HawkSearch ships a recommendations product; the theme already wires `WEBPACK_HAWKSEARCH_RECOMMENDATIONS_URL` ([search brief](search-experience-concepts.md)). | ✅ **Phase-3 recs engine.** We already pay for it — use it for FBT / "based on recent purchases" ranking rather than buying a second engine. |
| **Personalization apps** (Nosto, Rebuy, LimeSpot, Searchspring Recommendations) | Third-party recommendation/personalization engines. | ❌ **Skip.** A second recs subscription that fights Caster visually and parks behavior data in a proprietary format — when we already own the purchase data *and* have Hawk recs. At ~$300–1,000+/mo, the recurring fee passes the one-time custom build inside **~12–18 months** (Rule #3), and duplicates an engine we're already paying for. |
| **Replenishment / subscription apps** (RecurringCheckout, etc.) | Bolt-on subscribe-and-save. | ⏳ **Evaluate at Phase 3.** Auto-ship is a real feature, but sequence it after Buy Again proves reorder demand; revisit native BC subscriptions vs. app then. |

**Amortization note (Rule #3):** the Buy Again view and the cross-page modules are one-time builds against data we already store and an engine (Hawk) we already pay for. Any net-new personalization subscription is a recurring cost layered on top of capabilities we own — it fails the amortization test for the *recommendation* job and adds a Caster-foreign UI to maintain.

---

## Caster impact

**Reused as-is:** `.scroll-row` (the existing horizontal rail from [index-signed-in.html](../v2/index-signed-in.html)), `.product-card`, `.reorder-list` / `.reorder-row` (the reorder pattern from [shop-parts.html](../shop-parts.html)), `.section-header` (+ `__label` / `__desc` / `__link`), `.pill`, `.stock-badge`, `.btn`, `.qty-stepper`, `.acct-tabs`, `.filter-rail`.

**New patterns, inline-scoped in `v2/` first (Rule #7):**
- **`.reorder-row__freq`** — a frequency / cadence hint line on a reorder row ("bought 4× · last May 2 · ~every 6 wks · due now"). The atom that makes a reorder list feel personalized rather than historical.
- **`.buy-again-callout`** — the PDP "you bought a compatible part" inset (`.pill pill-accent` + `.btn btn-outline`).
- **`.cart-reorder-nudge`** — the cart "add your usual consumables" block (a `.reorder-list` variant with `.qty-stepper`).
- **`.guest-bridge`** — the signed-out "sign in to see everything you've bought" carrot card.

**Promotion (done in v0.16.3):** `.scroll-row` and `.reorder-list` / `.reorder-row` each hit their **second consumer** here, so they graduated from inline to `components.css` with showcase pages (`layout/scroll-row.html`, `composites/reorder-row.html`) and `decisions.md` entries D26/D27. The four feature-specific classes (`.reorder-row__freq` aside, which rode along as part of the reorder-row anatomy) stay inline until a second consumer appears.

---

## Phased recommendation

### Phase 1 — Surface what we already have

Make purchase history a shopping surface, and generalize the account beyond National Accounts.

1. **Account "Buy Again" view** — a deduped, line-item list aggregated *across* orders (not order-by-order), filterable by machine / category / site, each row with reorder + `.qty-stepper` + save-to-list. New tab in the account strip.
2. **Lean on native Reorder** for the per-order path; the new view aggregates over it.
3. **Generalize the framing** — every Buy Again surface reads from raw line-items, so a single-site / single-machine buyer with one past order gets the full experience; National extras (company-wide purchases, per-site filter) are additive.
4. **Guest bridge** — native recently-viewed rail (honest: items *viewed*) + a "sign in to see everything you've bought" card targeting the 39%/1.61% gap.

**What "winning" looks like:** reorder rate from the account climbs, login rate among returning visitors rises (guest-bridge conversion), and repeat-order time-to-cart drops.

### Phase 2 — Personalize the storefront

Take the deduped purchase list cross-page and add cadence.

- **"Based on recent purchases"** modules on home (recs rail), PLP ("you've bought from this category before"), PDP ("you bought a compatible part"), cart ("add your usual consumables").
- **Aggregation + cadence service** — compute frequency and inter-purchase interval per SKU to drive `.reorder-row__freq` and "due now" sorting.
- **Seed downstream features** — pre-fill saved-lists ([saved-cart](saved-cart-concepts.md) P2) and replenishment reminders from the same list.

**Trigger:** Phase 1 is live and reorder rate confirms demand; merch has begun populating `compatible_models[]` (shared with model-landing / search).

### Phase 3 — Predict & rank

- **Predictive replenishment + auto-ship** from purchase cadence.
- **Fleet-aware ranking** — reorder candidates ordered by what the account's registered machines consume (fleet ↔ `compatible_models[]` ↔ history).
- **FBT / "based on recent purchases" ranking via Hawk Recommendations** (already deployed).

**Catalyst-replatform candidate** — see below.

---

## Implementation notes

### Data sources
The whole feature reads from **order line-items**: Storefront GraphQL `customer.orders.lineItems` (or REST Orders v2) → dedupe by product/variant → aggregate `{ sku, name, orderCount, lastOrdered, intervalDays }`. For B2B-Edition companies, query at the company scope so the list spans buyers/sites. `compatible_models[]` joins history to machine fit for the PDP callout and fleet ranking.

### Auth / role gating
Buy Again and all "based on purchases" modules require a signed-in customer (or B2B company context); they render nothing for guests except the recently-viewed + bridge. In B2B Edition, respect role visibility — a junior buyer sees their own / their site's purchases per the account's permission model.

### All-accounts generalization
Default copy is account-neutral (no "National," "fleet," or "sites" framing) so the experience fits a one-location buyer. National-only affordances (company-wide purchases, per-site filter, fleet ranking) are **additive and degrade gracefully** — absent data simply hides the control.

### Accessibility
`.scroll-row` rails are keyboard-scrollable with visible focus and `aria-label`; reorder rows are a list with per-row actions labeled ("Add HEPA filter to cart"); qty steppers follow the existing `.qty-stepper` spinbutton semantics; the guest-bridge card is a labeled region, not an interstitial.

### Analytics
- Buy Again CTR and **reorder rate** (added-to-cart from a reorder surface ÷ views).
- **Guest-bridge login conversion** (the retention metric — moves the 1.61%).
- Cadence accuracy: predicted-due vs. actual reorder, to tune Phase 3.
- Module attribution: cart adds sourced from home rail / PLP / PDP / cart nudge.

### Mobile behavior
`.scroll-row` rails are already horizontal-scroll (native on touch); `.reorder-list` rows reflow to stacked on narrow widths; the account filter rail collapses above the list (matching `account-orders.html`'s mobile pattern). 37% of traffic is mobile.

---

## Catalyst / replatform candidacy

**Phase 1 + Phase 2 ship in Stencil.** Aggregating order line-items via the Storefront/REST APIs and rendering Caster modules is Stencil-native — the account view, the four storefront modules, and the guest bridge need no replatform.

**Phase 3 is the Catalyst case.** Predictive replenishment, fleet-aware result ranking, and rendering Hawk Recommendations as first-class components all want a server-side data layer and React rendering that Stencil expresses awkwardly (the same forcing function as [search](search-experience-concepts.md) P3 and [model-landing](model-landing-concepts.md) P3). Personalization is the through-line that, across these three briefs, justifies the headless move — but **none of them justify it alone**, and Phases 1–2 deliver the retention win now.

---

## Sources

- [USAClean_IA_Search_Roadmap.md](USAClean_IA_Search_Roadmap.md) — 39% returning / 1.61% login; roadmap item #15 "account hub for repeat B2B buyers" with order-history-and-reorder as a core feature
- [account-concepts.md](account-concepts.md) — the account dashboard this view plugs into; Phase-1 order history + saved lists
- [saved-cart-concepts.md](saved-cart-concepts.md) — Shopping Lists as the persistence target for "save as list"
- [search-experience-concepts.md](search-experience-concepts.md) — Hawk Recommendations already deployed; shared `compatible_models[]`; the personalization forcing function
- [model-landing-concepts.md](model-landing-concepts.md) — `compatible_models[]` data model for compatibility-aware reorder
- [feature-design-guidelines.md](feature-design-guidelines.md) — operating manual (eight rules + brief template)
- [BC — Reordering products](https://support.bigcommerce.com/s/article/Reordering-Products)
- [BC Storefront GraphQL — Customer & Orders](https://developer.bigcommerce.com/docs/storefront/graphql)
- [BC — Shopping Lists (B2B Edition)](https://support.bigcommerce.com/s/article/Shopping-Lists)
- [BC Stencil — Handlebars / recently viewed](https://developer.bigcommerce.com/docs/storefront/stencil/themes/context/handlebars-reference)
- [Amazon Business — Buy Again](https://business.amazon.com/) — deduped re-buy pattern
- [Grainger](https://www.grainger.com/) — order history + reorder lists
- [McMaster-Carr](https://www.mcmaster.com/) — order history as navigation
