Skip to content
Guides

Pages and routes

Create SSR routes with pages/, definePage, and serverApi.

Last updated:

The CLI turns files under pages/ into routes. Do not configure TanStack Router or Vite manually.

FileRoute
pages/index.tsx/
pages/products.index.tsx/products/
pages/products.$productSlug.tsx/products/:productSlug

UI pages use .tsx or .jsx. API routes use .ts and can export GET, POST, PUT, PATCH, and DELETE handlers.

Page with data

pages/products.$productSlug.tsx
import {
  definePage,
  serverApi,
  stripHtml,
  type ProductDetail,
} from "@ecomiq/storefront";

export default definePage<
  ProductDetail,
  { productSlug: string }
>({
  loader: ({ params }) => serverApi.getProductBySlug(params.productSlug),
  meta: ({ data }) => ({
    title: data.name,
    description: stripHtml(data.description).slice(0, 155),
    ogImage: data.images[0]?.url || data.imageUrl,
  }),
  component: ({ data, settings }) => (
    <article>
      <p>{settings.store.name}</p>
      <h1>{data.name}</h1>
      <p>
        {data.price} {settings.store.currency}
      </p>
    </article>
  ),
});

loader receives { params, search, config }. component receives { data, params, search, settings }. URL search changes rerun the loader by default; use reloadOnSearch: false when handling them on the client.

definePage also accepts:

  • preload
  • loading
  • error
  • meta with title, description, ogImage, noIndex, meta, and jsonLd

Global layout

layout.tsx is optional. When present, it replaces the framework's basic layout:

layout.tsx
import { defineLayout, Link, useMenu } from "@ecomiq/storefront";

export default defineLayout(({ children, settings }) => {
  const main = useMenu("main");

  return (
    <>
      <header>
        <Link to="/">{settings.store.name}</Link>
        <nav>
          {main.map((item) => (
            <Link key={item.url} to={item.url}>
              {item.label}
            </Link>
          ))}
        </nav>
      </header>
      <main>{children}</main>
    </>
  );
});

Reserved routes

The framework generates and protects these routes:

  • /api/* — same-origin API proxy
  • /api/auth/* — Better Auth
  • /checkout/ and /checkout/:checkoutId
  • /thank-you
  • /pages/:pageSlug
  • /account/, /account/addresses, and /account/orders
  • /account/orders/:orderId and /account/devices

Do not create files under pages/ that try to replace them. /account/devices is currently an informational screen: there is no endpoint for listing devices yet.

On this page