Custom page
A static content page the merchant creates in the dashboard, such as About us or Terms.
Framework: TanStack Start
This page is written for TanStack Start, the default and currently supported framework. See what depends on the framework.
The customer sees a page the merchant wrote in the dashboard (About us, Shipping policy, Terms): a title, the HTML body, and comments when the page allows them. Customise this route to style the content, use the SEO fields the merchant filled in, or read the page type. To add a page the merchant does not manage, see Add a page.
URL
| Pattern | Example |
|---|---|
/$slug/page-{$id} | /about-us/page-12 |
/{locale}/$slug/page-{$id} | /en/about-us/page-12 |
Params
| Name | Type | Example | Description |
|---|---|---|---|
slug | string | about-us | For readable links. Not passed to the loader. |
id | string | 12 | The page id, written page-12. |
locale | string | undefined | en | Optional (locale prefix). |
There are no search params.
Route file
// @auto-generated
import { createFileRoute } from '@tanstack/react-router';
import { PageSingle } from '@salla.sa/twilight-theme-engine/routes/page';
import type { PageSingleProps } from '@salla.sa/twilight-theme-engine/routes/page';
import { withHead } from '@salla.sa/twilight-theme-engine/tanstack';
export const Route = createFileRoute('/{-$locale}/$slug/page-{$id}')({
loader: ({ params }): Promise<PageSingleProps> =>
PageSingle.loader({ params: { id: params.id }, locale: params.locale }),
head: withHead(PageSingle),
component: PageSingleComponent,
});
function PageSingleComponent() {
const data: PageSingleProps = Route.useLoaderData();
return <PageSingle.Component {...data} />;
}To take the page over, write your own file and map the path in app/routes.ts: route('/$slug/page-{$id}', 'page-single.tsx'). The real theme does this to keep the page type, which the engine loader drops. See Fork a built-in route.
A customised version
This reads the page from the pages API to keep the type the engine drops, then reuses the engine head and component.
import { createFileRoute } from '@tanstack/react-router';
import { page as pageApi } from '@salla.sa/twilight-theme-engine/api/page';
import { PageSingle, type PageSingleProps } from '@salla.sa/twilight-theme-engine/routes/page';
import { withHead } from '@salla.sa/twilight-theme-engine/tanstack';
type Data = PageSingleProps & { type?: string };
export const Route = createFileRoute('/{-$locale}/$slug/page-{$id}')({
loader: async ({ params }): Promise<Data> => {
const data = await pageApi.findOrThrow(params.id);
return {
type: data.type,
page: {
slug: 'page-single', // the engine's id for this page
id: data.id,
title: data.name,
url: data.url,
content: data.content,
metadata: data.metadata,
created_at: data.created_at,
},
};
},
head: withHead(PageSingle),
component: PageComponent,
});
function PageComponent() {
const { type, ...data } = Route.useLoaderData();
return (
<>
{type === 'customised' && <p className="container">Custom layout page</p>}
<PageSingle.Component {...data} />
</>
);
}What it loads
PageSingleProps is one page object:
| Field | Description |
|---|---|
slug | The page identifier, page-single. |
id, title, url | From the API (name becomes title). |
content | The HTML body written in the dashboard. |
metadata | Optional SEO title and description. |
created_at | Optional published_time and modified_time. |
The loader runs on the server for the first request. The component renders content as raw HTML inside an element with the content-entry class (the hook for styling it), and adds the comments widget in the browser. See Loading data and the page API.
Head and SEO
Title is the page title and description is the body with tags stripped (not truncated). It does not read metadata; to use it, extend the head with withHead(PageSingle, (head, _ctx, data) => ...). Canonical and og:url are the page's url, the Open Graph type is article with published and modified times, and hreflang links are added. There is no JSON-LD. See SEO and head.
Edge cases
- Unknown page. Any API failure becomes a 404 error page, not the full not-found page.
- Wrong slug. Ignored, no redirect. The canonical tag is the page's own
url. - Merchant HTML. The body is injected as is.
- Page
type. The API returns it, but the engine loader does not pass it on; use the loader above to read it.