Pages and routes
Create SSR routes with pages/, definePage, and serverApi.
The CLI turns files under pages/ into routes. Do not configure TanStack Router
or Vite manually.
| File | Route |
|---|---|
pages/index.tsx | / |
pages/products.index.tsx | /products/ |
pages/products.$productSlug.tsx | /products/:productSlug |
pages/api/$.ts | /api/* |
UI pages use .tsx or .jsx. API routes use .ts and can export GET, POST,
PUT, PATCH, and DELETE handlers.
Page with data
import {
definePage,
serverApi,
stripHtml,
z,
type ProductDetail,
} from "@ecomiq/storefront";
const SearchSchema = z.object({
sellerId: z.string().uuid().optional(),
});
type ProductSearch = z.infer<typeof SearchSchema>;
export default definePage<
ProductDetail,
{ productSlug: string },
ProductSearch
>({
validateSearch: (raw) => SearchSchema.parse(raw),
loader: ({ params, search }) =>
serverApi.getProductBySlug(params.productSlug, {
sellerId: search.sellerId,
}),
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:
preloadloadingerrormetawithtitle,description,ogImage,noIndex,meta, andjsonLd
Global layout
layout.tsx is optional. When present, it replaces the framework's basic layout:
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>
</>
);
});Same-origin proxy
Customer accounts use an /api/* proxy. The starter defines it as follows:
import { proxyStorefrontRequest } from "@ecomiq/storefront";
type ApiHandlerArgs = {
request: Request;
params: { _splat?: string };
};
async function handle({ request, params }: ApiHandlerArgs) {
return proxyStorefrontRequest(request, params._splat);
}
export const GET = handle;
export const POST = handle;
export const PUT = handle;
export const PATCH = handle;
export const DELETE = handle;Reserved routes
The framework generates and protects these routes:
/checkout/and/checkout/:checkoutId/thank-you/pages/:pageSlug/account/,/account/addresses, and/account/orders/account/orders/:orderIdand/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.