Litestore · Developer docs
Storefront

Storefront pages

A storefront page is a database row with HTML content and an optional product feed, and the storefront routes render both with the store's design.

A storefront page in Litestore is a database row with some sanitized HTML and an optional product feed. The routes render the HTML first and the feed below it, with a filter rail that Litestore builds from the catalog. Read this page before you change how pages render, add a page-level feature or restyle the storefront.

your-store.com
Summer linenLight shirts and trousers for warm days. Everything here ships within two days.Content
CategoryPriceSizeOn sale
A page renders its content in a prose column, then its feed with the filter rail beside it.

Page model

FieldWhat it holds
nameThe page's name in the admin. It's also the default title in search results.
slugThe address of a custom page, which renders at /pages/{slug}. Old slugs are kept in previousSlugs.
pageEither "home" for the home page or "custom" for every other page.
contentThe page body as HTML. pageCrud sanitizes it before storing it.
feedIdThe product feed shown below the content, or null for no feed.
metaTitle, metaDescriptionOptional overrides for search results.
isActiveWhether shoppers can see the page.

Without a stored meta title, the page's SEO subject uses the name. Without a stored meta description, it uses roughly the first 155 characters of the content as plain text. Both live in modules/storefront/seo/subject.ts.

Routes

RouteWhat it renders
/The page with page: "home". Without that row, or when it has no feed, the route shows the store's default feed, so the home page is never blank.
/pages/[slug]A custom page. On a slug miss, the route checks the slug history and redirects permanently before it returns a 404.
/feedThe default feed with its filters. Header search lands here as /feed?q=….
/categories, /collectionsPlain code pages that list the live categories and collections. They aren't Page rows.
/productsRetired. next.config.ts redirects it to /feed when it has a q parameter and to /collections otherwise.

Both page routes call getStorefrontPage() in modules/storefront/pages/page.ts. Shoppers only get active pages, and they read them through a cache tagged storefront-pages. Staff also get inactive pages, so they can check a page before it goes live. In that case the route shows a preview banner and marks the page noindex.

Rendering

modules/storefront/pages/page-view.tsx exports the two parts every page uses.

PageContent sanitizes the HTML again at render time and puts it in a Tailwind Typography prose container. If nothing renderable is left after sanitizing, it renders nothing.

PageFeed renders the filter rail and the feed side by side. The rail is FeedFilters in modules/storefront/feed/feed-filters.tsx. It finds its facets in the catalog, in this order: category, price, every variant option, every spec, and on-sale when at least one product is discounted. The feed itself streams in a Suspense boundary and loads more products as the shopper scrolls.

Each facet writes its own URL parameter, so facets never overwrite each other. buildFilterFromSearchParams() in modules/storefront/feed/facets.ts reads them back into one filter object.

category      category filter
price         price range, written as min-max
o_<key>       a variant option, such as o_size
s_<key>       a spec, such as s_material
onsale        on-sale only
q             search text

Because the filter lives in the URL, the first page of the feed and every infinite-scroll page after it read the same filter. A filtered or sorted feed skips the shared feed cache, while the plain feed stays cached.

Page content

Every write goes through pageCrud in modules/storefront/admin/pages/crud.ts, whether it comes from the admin form or from the assistant. The CRUD class sanitizes the content, checks that the slug is free, rejects a feed that doesn't exist or isn't active, and rejects any change that would hide the home page. After each write it revalidates the page's route and the sitemap. The delete action rejects a request to delete the home page.

The admin page editor is PageContentCard. It uses the shared rich-text editor and saves the content when the editor loses focus. The feed picker and the visibility switch each save on change.

The assistant has two tools for pages in modules/assistant/agent-tools.ts. getStorefront is a read tool that lists every page and feed and can return one page's full HTML. savePage creates or updates a page, and it's an execute tool, so it only runs after an operator approves the proposal. Two more tools sit next to them: updateFeed changes how a feed orders or shows products, and setSeoMeta sets a page's (or another entity's) meta title and description, both behind the same approval. See Agents.

Design

The storefront has one design and no theme layer. There's no theme registry, no generated CSS and no appearance settings in the admin. The design tokens are CSS variables in the @theme block of app/styles.css.

/* app/styles.css */
--color-primary: #ea580c;
--radius: 0.5rem;
NewLinen shirtLight, breathable, made to last.$48.00Add to cart
The storefront reads its colours and corner radius from CSS variables in app/styles.css.
app/styles.css
@theme {
  --color-background: hsl(0 0% 100%);
  --color-foreground: hsl(0 0% 12%);
  --color-primary: hsl(24 95% 53%);
  --radius-default: 0.5rem;
}

To change the colours, corners or spacing, edit those variables. For anything the variables don't cover, edit the storefront components directly. They're ordinary React components in the repository.

To make a page render differently, for example with an extra section above the feed, change the route or page-view.tsx. Pages don't store layout, so a layout change is a code change that applies to every page.

On this page