The app is a Shopify embedded app on React Router 7 and Polaris, with Postgres (Prisma), Redis and a BullMQ worker. It has three extensions: a theme app embed for the drawer, a Discount Function for bundle discounts, and a Web Pixel for attribution. The AI engine is the Google Gemini API.
Key design decisions
- One rule engine, two runtimes. The bundle engine is one dependency-free JavaScript module. It is compiled into the Discount Function's WebAssembly and imported directly by the app server, so the drawer's "unlocked" and "add 1 more" messages come from the same allocation that checkout performs. Collection membership is checked live at checkout through
inCollections, with the collection IDs passed as input-query variables from a discount metafield.
- Predictable bundles. Rules are evaluated in priority order and complete sets are allocated greedily from the cheapest matching units. A unit is never discounted by two bundles, and a malformed rule is dropped rather than disabling the whole discount.
- A drawer that fits the app-embed budget. The embed is a 5.2 kB bootstrap that intercepts the theme's add-to-cart requests and forms, then lazy-loads the drawer UI on idle or first use. Bootstrap and UI together are about 9.5 kB of gzipped JavaScript plus 2.3 kB of CSS. The UI renders in a Shadow DOM, so theme styles can't leak in and the drawer's styles can't leak out.
- AI ranking with a fallback. Candidates are prefiltered from a Postgres catalog index to those that are active, published, in stock and not already in the cart. gemini-2.5-flash ranks up to 30 of them and writes a short reason for each pick. Observed latency is about 1.6 s, with an 8 s timeout and no retries. Any failure falls back to a rule-based ranking based on shared collections, tags and add-on price, and rankings are cached in Redis per cart.
- Secured storefront endpoint. The drawer talks to the app only through the Shopify App Proxy. Every request's HMAC signature is verified, and requests are rate-limited per IP and per shop.
- Anonymous attribution. A strict-sandbox Web Pixel forwards the drawer's custom events and
checkout_completed line items. An order line counts as attributed when the same browser added that product from a recommendation within 7 days. No customer names, emails, addresses or IDs are stored.
Architecture
The theme app embed loads the bootstrap, which reads the cart through Shopify's AJAX API and posts it to /apps/upsell/recommend. On the server, the proxy route verifies the signature and then runs recommendations (catalog index, scoring, AI rank, Redis cache) and bundle status (the shared engine) in parallel. The worker keeps the catalog index current with a bulk-operation full sync, debounced product webhooks and a daily resync, and publishes bundle rules to a single automatic app discount. The admin has Recommendations with a "Try it" tester, a bundle editor with product and collection groups, Countdown & design with a live drawer preview, Analytics, and Billing.