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

createTwilightRootRoute

functionAdvancedserverbrowser

Creates the root route of app/routes/__root.tsx with the engine's store-settings loading, global head tags and "Store Unavailable" error page.

import { createTwilightRootRoute } from '@salla.sa/twilight-theme-engine/tanstack';

In plain words

Every page of a theme sits inside one root route: it draws the <html> document and runs first on every navigation. createTwilightRootRoute() makes that route with three things already attached:

  • a step that loads the store settings, translations and installed apps before any page, because nothing can render without the settings;
  • the tags every page needs in <head>: the Salla SDK, fonts, icons, the favicon, and the store's title and description;
  • the "Store is temporarily unavailable" page, shown when the store cannot be loaded.

You call it twice in a row, createTwilightRootRoute()({ … }), and usually pass only shellComponent, the component that draws the document.

Signature

function createTwilightRootRoute(): (options: any) => RootRoute

// createTwilightRootRoute()(options) is
// createRootRouteWithContext<RouterInitialContext>()({
//   beforeLoad: rootBeforeLoad,          // settings, translations, apps, locale, auth token
//   head: rootHead,                      // SDK, import map, fonts, icons, base SEO tags
//   errorComponent: RootErrorComponent,  // "Store is temporarily unavailable"
//   ...options,                          // yours are spread last
// })

Example

app/routes/__root.tsx
import { HeadContent, Outlet, Scripts } from '@tanstack/react-router';
import { TwilightProvider } from '@salla.sa/twilight-theme-engine';
import {
  createTwilightRootRoute,
  getTwilightContext,
} from '@salla.sa/twilight-theme-engine/tanstack';
import themeTranslations from 'virtual:twilight/theme-translations';
import '../styles/app.css';

export const Route = createTwilightRootRoute()({
  shellComponent: RootComponent,
});

function RootComponent() {
  // The root beforeLoad has already written the locale for this request.
  const ctx = getTwilightContext();

  return (
    <html lang={ctx.locale} dir={ctx.dir} suppressHydrationWarning>
      <head>
        <meta charSet="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <HeadContent />
      </head>
      <body suppressHydrationWarning>
        <TwilightProvider translations={themeTranslations}>
          <Outlet />
        </TwilightProvider>
        <Scripts />
      </body>
    </html>
  );
}

How it behaves

  • The engine beforeLoad runs on every navigation, on the server and in the browser. It takes the locale from the {-$locale} param (falling back to ar), loads settings and translations through the QueryClient (cached, so a navigation rarely makes a request), loads app snippets and app settings (failures there are logged, not thrown), and writes settings, locale, i18n and the auth token into the twilight context.

  • It returns { settings, locale, dir, authToken, appsSnippets, appsSettings }, which every child route receives in context next to queryClient.

  • When the settings request returns nothing it throws SettingsError ("Failed to load store settings. Make sure VITE_STORE_DOMAIN is set."), and the error page takes over. On localhost and the preview host, a store named by ?storeId= or a cookie instead of the path gets a full-page redirect to /<username>/… once its username is known.

  • The root head falls back to the title "Store Unavailable" when there are no settings. Its base tags include a canonical link to the origin of the store's URL (buildBaseHead without a path); a page head that sets its own canonical adds a second link, because TanStack appends the links of every matched route.

  • The error page is fixed: <html lang="ar" dir="rtl">, English text and a Retry button that reloads, whatever the store's language.

  • This playground's own root (packages/playground/app/routes/__root.tsx) is built the same way. The difference that matters is layout={PlaygroundLayout} on TwilightProvider, which draws the docs chrome on playground routes and the engine MasterLayout everywhere else.

Gotchas

  • Your options are spread last, so passing beforeLoad, head or errorComponent replaces the engine's. rootBeforeLoad and rootHead are not exported, so you cannot wrap them: with your own beforeLoad nothing writes the settings, and every getTwilightContext().settings read throws SettingsError; with your own head the Salla SDK script, fonts and icons disappear. Pass shellComponent, and component or staticData if you need them.

  • The options are typed any, so TypeScript checks nothing you pass: a misspelt shellComponet compiles and is ignored. Compare the key with the example when the document renders without your shell.

Related

Source and docs