NUMU Docs
Contact
APIThemes
Partner Apps
APIThemes
Partner Apps
  1. Storefront host
  • 🗂️ Themes overview
  • 🚀 Start here
  • Glossary
  • Theme engine
    • Architecture
    • BYOT contract
    • Customizer
    • Federation runtime
    • Page data contract
    • Theme manifest
  • Theme SDK
    • Components
    • Federation helpers
    • Hooks
    • SDK overview
    • Type definitions
  • CLI & Vite plugin
    • CLI commands
    • CLI overview
    • Lint rules
    • Section library
    • Vite plugin
  • Storefront host
    • API proxies
    • Built-in fallbacks
    • BYOT fork
    • Routing
    • Storefront overview
  1. Storefront host

Built-in fallbacks

What the storefront renders when there's no BYOT theme installed.

Why they exist#

A merchant can be in any of these states:
1.
Just signed up. No theme installed, no customizer config. Storefront should still load.
2.
Built-in theme (bazar). Customizer configured but no BYOT bundle.
3.
BYOT installed but a customer hits a route the theme doesn't handle (e.g. /account/gift-cards on a theme that doesn't ship that page).
4.
BYOT installed and the bundle fails to load (CDN outage, bad URL).
Built-in fallbacks cover 1, 2, and 4. Case 3 is handled by the bundle returning null for unknown page types — the storefront's per-route fallback takes over for that one route.

Layers#

When the BYOT branch doesn't fire, the route picks one of three renderers:

1. Built-in template (preferred)#

themeSettings.templates.<page_type> — the customizer's per-page section list.
const productTemplate = themeSettings.templates?.product;
if (productTemplate) {
  return (
    <PageTemplateRenderer
      template={productTemplate}
      themeId={themeSettings.theme_id}
      storeData={store}
    />
  );
}
PageTemplateRenderer iterates template.order and renders each section via SectionLoader, which dynamically imports from the built-in theme's section registry (ThemeRegistry.ts).
The bazar theme's current registry:
{
  bazar: {
    hero, "featured-products", categories, banner,
    "rich-text", "image-with-text", newsletter, testimonials,
    "product-grid", slideshow, "video-section", "collection-list",
    "contact-form", faq, "logo-list", "map-section", "multi-column",
  }
}
Plus shared:
{
  header, footer, "announcement-bar"
}

2. Built-in React component#

When no template is configured, the storefront renders a hard-coded React component. Currently:
RouteComponent
/products/[slug]BuiltInProductDetail
/cartBuiltInCart
/account/orders/[id]OrderDetail (from components/account/Dashboard)
/account/gift-cardsGiftCardCheckClient
These are deliberately minimal: tasteful defaults that show every Phase 8 feature works (variant picker, gift card UI, reorder button) without requiring a theme.

3. Generic placeholder#

For routes that have neither template nor built-in component (e.g. /account/wishlist currently), the route renders a simple "No template configured" placeholder. Vanishingly rare — we're upgrading these as we hit them.

BuiltInProductDetail#

Phase 8.1 + 8.5. Self-contained, no SDK context dependency. Talks to /api/cart/add directly the same way NuMuProvider does.
<BuiltInProductDetail product={product} />
Renders:
Product image (defaulting to first variant image when one is selected)
Name + price + compare-at-price
Description
Option-axis swatches (Size, Color) with greys-out for unavailable combos
Quantity stepper
Add-to-cart button with state machine (idle → adding → idle)
Toast on add success + window dispatchEvent(numu:cart:updated)
The variant resolution logic is duplicated here (instead of importing from the SDK) because the built-in components must work even when the SDK runtime hasn't loaded.

BuiltInCart#

Phase 8.1 line labels + Phase 8.3 gift cards + Phase 8.4 promotions. Loads /api/cart on mount + on every numu:cart:updated event.
Renders:
Per-line item: image, product name, variant_name label (e.g. "Size: M / Color: Red"), qty stepper, remove, line total
Per-line warnings: "Sold out — remove to continue", "Price changed — now X"
Footer: subtotal, applied promotion (if any), total
"Shipping, taxes, and any gift cards apply at checkout" hint
Checkout CTA

OrderDetail + ReorderButton#

/account/orders/[id] server-renders the order with OrderDetail, plus a client-island ReorderButton. The button hits /api/customer/orders/{id}/reorder and shows added-count + per-line skip reasons in an inline banner.

GiftCardCheckClient#

/account/gift-cards v1 — form-driven balance probe. Lists-of-customer's-gift-cards needs a backend endpoint (gift_card.customer_id is populated but no listing route yet). When that lands, this page upgrades to show owned cards inline.

Built-in vs BYOT — coexistence#

A merchant always has a "current" theme. The built-in bazar theme is installed by default on new stores. From the customizer's perspective, both bazar and a published BYOT theme are equally configurable.
The split:
bazar's sections are registered in numu-storefront's ThemeRegistry.ts. They render server-side via Next.js components.
BYOT's sections are registered in the theme bundle. They render client-side via mount().
Switching between bazar and a BYOT theme is a hub click. The customizer state (templates + settings) carries across — that's what resolveThemeSettings() normalizes.

Adding a new built-in component#

When we add a new account page (say /account/wishlist):
1.
Build the client component at src/components/account/<Name>.tsx
2.
Wire it into the page route's built-in branch:
if (isByot) return byotElement;
if (template) return <PageTemplateRenderer ... />;
return <Wishlist customer={customer} items={items} />;
3.
Update Built-in Fallbacks (this page) so future devs know it exists
4.
Make sure the page-type union includes the new type (so theme devs can override it via BYOT)
The goal: every storefront route has a sane built-in fallback so a vanilla store works without any theme work at all.
Modified at 2026-09-19 15:53:15
Previous
API proxies
Next
BYOT fork
Built with