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

Routing

All store-scoped routes live under app/[domain]/. Below is what each route owns, what data it fetches SSR, and what it passes to the BYOT bundle.

Home — [domain]/page.tsx#

StepWhat
SSR fetchStore + theme settings
page.type"home"
page.datanone (themes fetch products/collections via hooks)
CacheISR revalidate: 60

PDP — [domain]/products/[slug]/page.tsx#

StepWhat
SSR fetchStore + theme + product (fetchProductBySlug → /products/{slug})
page.type"product"
page.data{ product: Product } (includes options[] + variants[])
Built-in fallbackBuiltInProductDetail with variant picker + qty + add-to-cart
CacheISR revalidate: 300, tag: product:{storeId}:{slug}

PLP — [domain]/collections/[slug]/page.tsx#

StepWhat
SSR fetchStore + theme + collection + products in collection
page.type"collection"
page.data{ collection: Category, products: Product[], pagination: {...} }
CacheISR revalidate: 60, tag: collection:{storeId}:{slug}

Cart — [domain]/cart/page.tsx#

StepWhat
SSR fetchStore + theme (no cart pre-fetch — useCart() runs client-side)
page.type"cart"
page.datanone
Built-in fallbackBuiltInCart — line items, qty controls, applied promotion, totals

Checkout — multi-step (Phase 7)#

Each step is its own route with its own BYOT fork.

Contact — [domain]/checkout/page.tsx#

page.type: "checkout_contact"
page.data: { cart, customer }
Customer might be null (guest checkout)

Shipping — [domain]/checkout/shipping/page.tsx#

page.type: "checkout_shipping"
page.data: { cart, address, rates, pickup_locations }
Calls /api/shipping/options for rates + /api/storefront/pickup-locations for pickup tab

Payment — [domain]/checkout/payment/page.tsx#

page.type: "checkout_payment"
page.data: { cart, address, methods, saved_cards }
Methods filtered to those the merchant has enabled (via /api/storefront/checkout-config)
Saved cards loaded only for token-charge-capable gateways (Paymob, Kashier)
Built-in form includes the Phase 8.3 gift-card input

Review — [domain]/checkout/review/page.tsx#

page.type: "checkout_review"
page.data: { cart, address, rate, method }
Submit → POST /api/checkout

Processing — [domain]/checkout/processing/page.tsx#

page.type: "checkout_processing"
page.data: { order_id }
Polls /api/customer/orders/{id} every 2s until payment_status="paid" or terminal failure
Used when the gateway redirects back here mid-payment

Thank-you — [domain]/checkout/[order_id]/thank-you/page.tsx#

page.type: "checkout_thank_you"
page.data: { order }
Terminal page — order_completed funnel event fires here for online payments

Account routes — [domain]/account/*#

Routepage.typepage.dataGuarded
/accountaccount{ customer, orderCount, addressCount }redirect to /login if no customer
/account/loginaccount_login{}no
/account/registeraccount_register{}no
/account/recoveraccount_recover{}no
/account/resetaccount_reset{ token }no
/account/profileaccount_profile{ customer }yes
/account/ordersaccount_orders{ customer, orders, pagination }yes
/account/orders/[id]account_order{ customer, order }yes (404 if not customer's order)
/account/addressesaccount_addresses{ customer, addresses }yes
/account/gift-cardsaccount_gift_cards{ customer }yes
/account/wishlistaccount_wishlist{ customer, items }yes
Auth happens server-side via the customer_access_token cookie. The proxy forwards it to NUMU-api's /storefront/me/* routes which return 401 on miss → storefront redirect("/account/login").

Search — [domain]/search/page.tsx#

StepWhat
SSR fetchStore + theme + search results from /storefront/store/{id}/search?q= (Phase 4.1 tsvector)
page.type"search"
page.data{ query, products, collections, pagination }

Content routes#

Pages — [domain]/pages/[handle]/page.tsx#

page.type: "page"
page.data: { page: { title, body_html, handle } }
Resolves from store.pages[handle] — merchant-authored static pages

Policies — [domain]/policies/[handle]/page.tsx#

page.type: "policies"
page.data: { policy: { handle, title, body_html } }
handle ∈ { privacy, refund, terms, shipping }
Stub-policies merchant can edit in hub Settings → Legal

Blogs — [domain]/blogs/page.tsx, [domain]/blogs/[handle]/page.tsx, [domain]/blogs/[blog]/[article]/page.tsx#

page.type: "blogs" | "blog" | "article"
Blogs backend lands in Phase 8.9. Current state: stubs that render gracefully when the backend returns empty.

Special#

Password gate — [domain]/password/page.tsx#

page.type: "password"
The middleware redirects all routes to /password when store.password_protected.enabled is true and the visitor doesn't have the unlock cookie.

404 — [domain]/not-found.tsx#

page.type: "404"
BYOT-forks like the others. Themes that ship a not_found section template render their custom 404. Built-in renders a generic "Page not found".

Error — [domain]/error.tsx#

Loads BEFORE the SDK federation runtime. Renders a static HTML template specified in theme.json's error_template_url (Phase 7.3). Falls back to a generic platform error page when missing.

Loading — [domain]/loading.tsx#

Same shape as error. Uses loading_template_url from theme.json.

SEO#

[domain]/sitemap.xml/route.ts — enumerates products/collections/pages dynamically
[domain]/robots.txt/route.ts — respects store.settings.seo.robots
Per-route generateMetadata() populates <title>, <meta>, OG tags

Locale URL prefix — Phase 6#

The middleware honors /ar/... prefixes:
/ar/products/blue-shirt → strips /ar, sets numu_locale=ar cookie, rewrites to /products/blue-shirt
The route handlers don't see the /ar prefix — they always render the rewritten path. The cookie drives useLocalization() + useDirection() (RTL when ar).

Adding a new route#

1.
Create app/[domain]/<your-path>/page.tsx
2.
SSR-fetch any data
3.
Resolve via byot-fork.tsx:
const fork = await resolveByotFork(domain, {
  type: "my_route_type",
  data: { ...whateverTheBundleNeeds },
});
if (fork.kind === "missing-store") notFound();
if (fork.kind === "byot") return fork.element;
return <MyBuiltInFallback />;
4.
Add "my_route_type" to the page-type union in Pages Data Contract
5.
Update the theme contract docs so theme devs know to handle it (or fall back to built-in)
Modified at 2026-09-24 13:03:12
Previous
BYOT fork
Next
Storefront overview
Built with