Skip to content
Twilight React Playground
ثيم رائدaren

Home

route-moduleBeginnerserverbrowserlive demo

The store's home page: loads the blocks the merchant arranged and draws each one with the component registered for its path.

import { Home, homeLoader, HomeSkeleton, HomeLoaderData, HomePageProps } from '@salla.sa/twilight-theme-engine/routes/home';

In plain words

In the Salla dashboard a merchant builds the home page from blocks: a slider, a banner, a row of products. Home.loader asks the API for that list, and Home.Component draws the blocks in order, each with the component your theme registered for that kind of block.

HomeSkeleton is the grey placeholder the home route shows while the list loads during a client navigation.

See the whole page running on Built-in pages.

Signature

const Home: {
  readonly id: 'index';
  readonly loader: (ctx?: { locale?: string }) => Promise<HomeLoaderData>;   // no extend
  readonly head: (ctx: TwilightContext) => HeadDescriptor;                   // ignores loader data
  readonly Component: React.MemoExoticComponent<(props: HomePageProps) => JSX.Element>;
};

function homeLoader(ctx?: { locale?: string }): Promise<HomeLoaderData>;
const HomeSkeleton: React.MemoExoticComponent<() => JSX.Element>;

interface HomeLoaderData {
  locale: string;                   // ctx.locale, or 'ar'
  components: HomeComponentData[];  // the blocks, "home." removed from each path
  page: { slug: string };           // 'index'
}

interface HomePageProps {
  locale?: string;
  components?: HomeComponentData[];
}

Try it live

The engine's home page drawn from this store's real block list, cut to the first few blocks, and the skeleton its route shows while that list loads.Try this: switch to pending to see HomeSkeleton, then raise the block count: blocks after the third mount only when scrolled into view.
Storefront canvas · ar · RTL
Controls
What a theme writes
import { createFileRoute } from '@tanstack/react-router';
import { Home } from '@salla.sa/twilight-theme-engine/routes/home';
import type { HomeLoaderData } from '@salla.sa/twilight-theme-engine/routes/home';
import { HomeSkeleton } from '@salla.sa/twilight-theme-engine/skeleton';
import { withHead } from '@salla.sa/twilight-theme-engine/tanstack';

// app/routes/index.tsx, as the plugin generates it (without its first line)
export const Route = createFileRoute('/{-$locale}/')({
  loader: ({ params }): Promise<HomeLoaderData> => Home.loader({ locale: params.locale }),
  head: withHead(Home),
  pendingComponent: () => <HomeSkeleton />,
  component: HomeComponent,
});

function HomeComponent() {
  const data: HomeLoaderData = Route.useLoaderData(); // <- drawn now
  return <Home.Component {...data} />;
}

Example

app/routes/index.tsx (generated)
import { createFileRoute } from '@tanstack/react-router';
import { Home } from '@salla.sa/twilight-theme-engine/routes/home';
import type { HomeLoaderData } from '@salla.sa/twilight-theme-engine/routes/home';
import { HomeSkeleton } from '@salla.sa/twilight-theme-engine/skeleton';
import { withHead } from '@salla.sa/twilight-theme-engine/tanstack';

export const Route = createFileRoute('/{-$locale}/')({
  loader: ({ params }): Promise<HomeLoaderData> => Home.loader({ locale: params.locale }),
  head: withHead(Home),
  pendingComponent: () => <HomeSkeleton />,
  component: HomeComponent,
});

function HomeComponent() {
  const data: HomeLoaderData = Route.useLoaderData();
  return <Home.Component {...data} />;
}

How it behaves

  • URL: /{-$locale}/. The generated route imports HomeSkeleton from @salla.sa/twilight-theme-engine/skeleton; it is the same component.

  • homeLoader reads the list with queryClient.ensureQueryData(home.queries.components()), so a list already in the query cache is returned without a request. It removes the first home. from each block's path (home.enhanced-slider becomes enhanced-slider), which is the name themes register their own blocks under.

  • Home.loader passes only locale to homeLoader. HomeLoaderData.locale is not used by the component.

  • head ignores the loader data. Title, description and keywords come from the store's SEO settings (store.meta, falling back to the store name and description), canonical from the store URL, the Open Graph and Twitter image from the logo, plus hreflang alternates. It returns {} when the context has no store.

  • Home.Component renders the slots home:start and home:content, one HomeComponentRenderer per block, then home:end. The first three blocks render at once; later ones mount when scrolled near (see HomeComponentRenderer).

  • To show the blocks outside the home route, read them in a component with useQuery(home.queries.components()) and remove home. from each path, as the demo does. homeLoader belongs in loaders.

  • In development the dev settings widget can overlay edits on the blocks; that code is removed from production builds.

Gotchas

  • Nothing registers blocks for you. Without registerHomeComponents(...) in app/router.tsx every block resolves to nothing: a yellow "Unknown component" card in development, an empty space in production. See the home block registry.

  • HomeComponentRenderer logs [HomeComponentRenderer] MOUNT and UNMOUNT with console.log for every block, in production too: that effect is not behind a development check (packages/theme-engine/src/components/home/HomePageRenderer.tsx).

Related

Source and docs