Guides · November 15, 2022
Anatomy of a Production Next.js Storefront (Ours)
A production Next.js storefront is a specific set of decisions — rendering strategy per route, cart/session architecture, cache invalidation, and a real commerce backend behind it. Here is how we are architecting ours, and what to check before you build or buy one.
By Polo Themes
A production-grade Next.js storefront is not "a Next.js app with a shopping cart bolted on." It is a specific set of architectural decisions made deliberately: which routes render at request time versus build time, how cart and session state survive a page reload without a login, how the cache invalidates the instant a price or inventory count changes, and how the whole thing talks to a real commerce backend instead of a pile of client-side fetch calls. This is a walkthrough of that anatomy — the actual decisions, in the order you have to make them — using the storefront we are building at Polo Themes as the running example. We are not selling a Next.js starter today; we are documenting the architecture as we build toward one, so that if you are evaluating headless commerce starters right now, you know exactly what to interrogate before you commit to any of them.
Why "Just Use Next.js" Undersells the Problem
Next.js gives you routing, rendering primitives, and a deployment story. It does not give you a commerce data model, a cart that survives across devices, a checkout flow that handles tax and shipping correctly, or a cache strategy that keeps a product page fast without showing a stale price. Every one of those is a decision a storefront team has to make explicitly, and the decisions interact with each other — the rendering strategy you pick for a product page constrains how you invalidate its cache, which constrains how fast a price change can propagate, which constrains whether you can trust the storefront during a flash sale. Treating "Next.js storefront" as a single checkbox is how teams end up with a fast home page and a checkout flow that silently shows the wrong price to five percent of customers.
Decision One: Rendering Strategy Per Route, Not Per App
The single biggest architectural mistake we see in headless commerce builds is choosing one rendering mode for the whole app. A storefront has at least four distinct route shapes, and each wants a different answer.
- Marketing and category landing pages — mostly static, changes infrequently, best served from a static or ISR (incremental static regeneration) build with a long revalidation window.
- Product detail pages — content is semi-static (description, images) but price and stock are live. This is the route that most punishes a one-size-fits-all decision: pure static generation goes stale on price; pure server-side rendering on every request adds latency you don't need for the parts of the page that never change.
- Cart and account pages — inherently per-user, must be dynamic, and should never be cached at the CDN layer keyed on a shared URL.
- Checkout — dynamic, security-sensitive, and the one place where you should bias toward correctness and observability over shaving milliseconds.
The practical pattern that holds up in production is ISR for product and category pages with a short revalidation window (measured in seconds to a couple of minutes, not hours), combined with on-demand revalidation triggered by a webhook from the commerce backend the moment a price, stock level, or publish state changes. That gives you static-page speed with correctness that doesn't depend on a fixed timer. Cart, account, and checkout stay server-rendered or client-rendered per request — you are trading a small amount of latency for the guarantee that a logged-in shopper never sees another shopper's cached cart.
Decision Two: Cart and Session Architecture
Cart state is deceptively hard to get right in a headless setup because it has to satisfy two audiences at once: a guest who has never logged in, and a returning customer who expects their cart to follow them across a phone and a laptop. The anatomy that works is a server-owned cart identified by an opaque cart ID stored in an HTTP-only cookie, with the cart record itself living in the commerce backend rather than in browser storage or a Next.js route handler's memory. The frontend never invents cart logic — it reads and mutates a cart resource through the backend's API, and every mutation (add item, apply discount, update quantity) round-trips through that same API so tax, promotion, and inventory rules are evaluated in exactly one place.
The failure mode we consistently see in DIY builds is a cart that is partially client state (for instant UI feedback) and partially server state (for correctness), with no single source of truth. That works fine in a demo and breaks the day someone opens the cart in two tabs, or a promotion expires mid-session. If you want instant-feeling UI, do it with optimistic updates on top of the server cart — update the UI immediately, then reconcile against the server response — not by maintaining a second, independent copy of cart logic in the browser.
Decision Three: The Commerce Backend Contract
A Next.js storefront is a rendering and interaction layer in front of a commerce backend that owns products, pricing, promotions, inventory, orders, and payment orchestration. The quality of that backend's API contract determines how much of the "hard" commerce logic the frontend team ends up quietly reimplementing — and reimplementing pricing or promotion logic in the frontend is a recurring source of bugs, because now two systems can disagree about what a customer owes.
A contract worth building against gives you: a GraphQL or REST API with first-class product, variant, and price objects (not just a flat SKU list); server-side cart and checkout endpoints so the frontend never calculates totals itself; webhook events for inventory, price, and order-status changes so the frontend can invalidate cache on-demand instead of polling; and a clean separation between publishable, storefront-safe credentials and admin credentials that should never reach a browser bundle. Whether that backend is a headless platform, a modular commerce engine, or a custom service, judge it on that contract before judging it on marketing copy.
Decision Four: Cache Invalidation Is the Real Product
Static generation and ISR make a Next.js storefront fast. They also introduce a new failure category that server-rendered-everything architectures don't have: stale correctness. A product page cached for ten minutes that doesn't know a price just changed is not a performance win, it's a liability. The architecture has to close that loop explicitly:
- A short default revalidation window on commerce-sensitive pages, so staleness is bounded even if the invalidation path fails.
- An on-demand revalidation endpoint that the commerce backend calls via webhook the instant something changes — price, stock, publish status, or a promotion window opening or closing.
- Tag-based or path-based cache invalidation granular enough that changing one product doesn't force a full-site rebuild.
- A monitoring signal (even a simple log line) when revalidation fails, so stale pricing shows up as an alert, not as a customer complaint.
Teams that skip this and rely purely on a fixed revalidation timer are making an implicit bet that nothing time-sensitive (a flash sale, a stock-out, a price correction) will ever need to propagate faster than that timer allows. That bet loses eventually, usually during the exact high-traffic moment when it's most visible.
Decision Five: Where the Money Moves
Checkout is the one part of the anatomy where you should actively resist building custom logic. Payment orchestration, PCI scope, tax calculation across jurisdictions, and fraud signals are solved problems with real regulatory weight behind them — a homegrown checkout flow is rarely the differentiator a storefront needs, and it is one of the most expensive places to get wrong. The pattern that holds up is to keep checkout thin on the frontend: collect input, hand off to a payment provider's hosted or embedded flow, and let the commerce backend own order creation and confirmation once payment succeeds. The frontend's job is presenting state clearly and handling the handful of edge cases (payment declined, session expired, item went out of stock mid-checkout) gracefully — not reimplementing payment logic.
Where Polo Themes Fits Today — and Where We're Headed
Everything above is the architecture we are actively building toward for a production-grade Next.js and headless-commerce starter line at Polo Themes. We are not shipping that as a purchasable product yet, and we won't claim otherwise — but it is a committed direction, not a maybe. Our reasoning for building it comes directly from the theme work we already do: our existing catalog of Figma and Shopify themes (including Optics, Medical, and the WOSA theme) has taught us, category by category, where prebuilt storefronts tend to fight the merchant — rigid variant pickers, cache strategies that weren't designed for a live catalog, checkout flows that don't leave room for the trust signals a specific category needs. A Next.js starter built on the anatomy above is our answer to those same problems for teams that want a headless, code-owned storefront rather than a themed platform install.
In the meantime, if you're evaluating options today, our existing Shopify theme catalog and Figma UI kits are live and purchasable now, and they're worth a look if a themed platform storefront — rather than a fully custom headless build — fits your current stage. If you want the broader thinking behind these architectural calls as we publish it, our blog is where that will land first.
A Checklist for Evaluating Any Next.js Storefront Starter
Whether you're looking at ours once it ships, or evaluating another headless commerce starter today, run it through these questions before you commit:
- Does it apply a different rendering strategy per route type, or one strategy for the whole app?
- Is cart state server-owned with a single source of truth, or split between client and server logic that can disagree?
- Does cache invalidation happen on-demand via webhook, or only on a fixed timer?
- Does the commerce backend own pricing, tax, and promotion calculation, or does the frontend duplicate any of that logic?
- Is checkout a thin client over a real payment provider, or a custom-built flow carrying PCI scope you didn't ask for?
- Can you point to the exact API contract (product, cart, order, webhook shapes) before you write a line of frontend code against it?
Frequently Asked Questions
Is Next.js actually a good fit for e-commerce, or is that overselling it?
Next.js is a strong fit specifically because it lets you mix rendering strategies per route — static for marketing pages, ISR for product pages, fully dynamic for cart and checkout — inside one app. The risk isn't the framework; it's treating commerce-specific problems (cache invalidation, cart correctness, checkout scope) as solved by the framework alone when they require deliberate architecture on top of it.
Does Polo Themes sell a Next.js storefront today?
Not yet. We currently sell Shopify themes and Figma UI kits (see our themes catalog). A production Next.js and headless-commerce starter line is a stated direction we're building toward, and we'll be transparent here on the blog as it moves from architecture to shipped product.
What's the single biggest mistake teams make building a headless storefront?
Picking one rendering strategy for the whole app instead of one per route, and relying on a fixed cache timer instead of on-demand invalidation. Both mistakes are invisible in a demo and expensive the first time a price or stock level needs to update faster than the timer allows.
Should I build checkout myself if I'm already building a custom headless frontend?
Generally no. Keep checkout thin on the frontend and let a payment provider and your commerce backend own payment orchestration, tax, and order creation. The custom value in a headless build almost always belongs in browsing, merchandising, and content — not in reimplementing checkout.