Add a page
Write a route file, then list it in app/routes.ts, to serve a page of your own at /ar/about and /en/about.
Goal: a page of your own, such as an About page, served at /ar/about and /en/about inside the store's header and footer.
Mechanism: a page is a route: a URL path paired with a file that says how to load and draw it. The engine generates the route files of its built-in pages for you. For a new page you write the file yourself in app/routes/, then list it in app/routes.ts. When the dev server or a build starts, the engine merges your list with its own pages and hands the result to TanStack Router, which adds the language segment in front.
1. Try it
The page's component, drawn here with the demo store's name, logo and description. The route file in step 3 only loads its title and renders it.
من نحن
<p class="ql-direction-rtl">هذا المتجر التجريبي يتيح لك استكشاف شكل وتصميم المتاجر على منصة <strong>سلة</strong>. تصفّح الأقسام، جرّب تجربة الشراء، واستعرض الميزات كما لو كنت عميلاً حقيقياً.</p>
2. Write the page component
import { useStore } from '@salla.sa/twilight-theme-engine/hooks/useStore';
/** What the About page draws. Its route file (app/routes/about.tsx) hands it the title. */
export function AboutPage({ title }: { title: string }) {
const { name, description, logo } = useStore();
return (
<main className="container py-8">
<h1 className="mb-4 text-2xl font-bold">{title}</h1>
<div className="flex items-center gap-4">
{logo && <img src={logo} alt={name} width={64} height={64} className="rounded-md" />}
<p className="leading-7">{description || name}</p>
</div>
</main>
);
}
3. Write the route file, before listing it
import { createFileRoute } from '@tanstack/react-router';
import {
getTwilightContext,
withHead,
type TwilightContext,
} from '@salla.sa/twilight-theme-engine/tanstack';
import { AboutPage } from '../components/AboutPage';
interface AboutData {
// Engine pages all return `page`: route::changed and the Salla SDK read it after a navigation.
page: { slug: string; title: string };
}
// The same shape as an engine route module's head, so withHead() can turn it into tags.
const About = {
head: (_ctx: TwilightContext, data: AboutData) => ({ title: data.page.title }),
};
// '/{-$locale}' + the path you list in app/routes.ts
export const Route = createFileRoute('/{-$locale}/about')({
loader: (): AboutData => {
const { locale } = getTwilightContext();
return { page: { slug: 'about', title: locale.startsWith('ar') ? 'من نحن' : 'About us' } };
},
head: withHead(About),
component: AboutRoute,
});
function AboutRoute() {
const { page } = Route.useLoaderData();
return <AboutPage title={page.title} />;
}
loaderreturns the page's data. It runs on the server for the first visit and in the browser on later navigations.headsets the<title>from that data.componentreads the data withRoute.useLoaderData()and draws the page.
4. List it in app/routes.ts, then restart the dev server
import { route } from '@tanstack/virtual-file-routes';
// A flat list of route(path, file). The file is looked up in app/routes/.
export const routes = [route('/about', 'about.tsx')];
In engine terms
twilightReact() reads app/routes.ts once, when Vite creates its plugins: it transpiles the file with esbuild and evaluates it in Node with a require rooted at the project (loadRoutesFile in src/vite/adapters/tanstack.adapter.ts). Each route(path, file) goes into a map keyed by path on top of DEFAULT_BASE_ROUTES, so a new path adds a page and an existing one replaces the engine's. buildVirtualRouteConfig nests every route under {-$locale} (paths starting with /account/ under the account layout) and passes the tree to TanStack Start as virtualRouteConfig. The locale wrapper redirects /about to /ar/about on a multilingual store and /ar/about to /about on a single-language one.
Traps
Go deeper: route modules, withHead, getTwilightContext, the lesson URLs, routes and pages, and Fork a built-in page to take over a page the engine already has.