Fork a built-in route
Keep the engine's loader, head and skeleton for a page, but render your own component.
Framework: TanStack Start
This page is written for TanStack Start, the default and currently supported framework. See what depends on the framework.
Use this when a built-in page (cart, product, a listing) needs a different layout, but you do not want to rewrite its data fetching.
Goal: the cart page renders your CartPage component, with the engine still supplying data, title and loading state.
Every built-in page has a loader (fetches data on the server), a head (title and meta tags) and a component. You take the route file over, reuse the engine's loader and head, and swap only the component. The reference theme does this for /cart, /$slug/p{$id} and /$slug/page-{$id}: its app/routes/cart.tsx, $slug.p$id.tsx and $slug.page-$id.tsx have no // @auto-generated line. Background: Routes and pages, Loading data.
Take the route file over
Open app/routes/cart.tsx and delete its first line, // @auto-generated. The build then leaves the file to you.
Some routes have no file in app/routes/, because the engine serves them from its own .twilight/ folder: every product list (search.tsx, products.tsx, $slug.c$id.tsx, tags.$id.tsx, $slug.tag-$id.tsx, brands.$id.tsx), reviews.tsx and the redirects from old Twig addresses. For those, create the file with the same name in app/routes/, without that line, and the build uses yours. The Routes pages show each engine file to start from.
Or keep your file under another name and point the engine's path at it from app/routes.ts. The path string must match the engine's exactly:
import { route } from '@tanstack/virtual-file-routes';
export const routes = [route('/cart', 'cart-page.tsx')];Reuse the engine's loader, head and skeleton
import { createFileRoute } from '@tanstack/react-router';
import { Cart } from '@salla.sa/twilight-theme-engine/routes/cart';
import type { CartPageProps } from '@salla.sa/twilight-theme-engine/routes/cart';
import { CartSkeleton } from '@salla.sa/twilight-theme-engine/skeleton';
import { withHead } from '@salla.sa/twilight-theme-engine/tanstack';
import { CartPage } from '../components/cart/CartPage';
export const Route = createFileRoute('/{-$locale}/cart')({
loader: ({ params }): Promise<CartPageProps> => Cart.loader({ locale: params.locale }),
head: withHead(Cart),
pendingComponent: () => <CartSkeleton />,
component: CartComponent,
});
function CartComponent() {
const data: CartPageProps = Route.useLoaderData();
return <CartPage {...data} />;
}Build your component
CartPage receives the loader data as props. Keep the engine's hook slots so apps that inject content still work. The engine's cart page renders cart:start, cart:items.start, cart:items.end and cart:end itself, and its CartSummary (from @salla.sa/twilight-theme-engine/cart) renders cart:summary.start and cart:summary.end.
Check it worked
Open /cart with an item in the cart: you see your markup, the page title is still set, and the engine's skeleton shows while it loads.
Traps
- You now own the page. If you only need extra content, use a slot instead: Add content to a slot.
- With
app/routes.ts, the engine's generated route file stays in the tree, unused. That is expected; do not edit it. - For a product list, fork nothing. To restyle every list at once, register your page as
product:list-page: Replace the whole product list page. - Components are reused across param-only navigation (product to product), so state and mount-once effects belong to the first product. The reference theme returns
<ProductPage key={data.product.id} {...data} />so everything remounts per product. - Dropping slots silently breaks apps that inject at them. Re-render the ones the engine page had.
- Never add
// @auto-generatedto your route file, or the next build overwrites it.
Next
- Cart route and Product route: what each built-in page loads.
- Replace the product card: change cards without forking pages.