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

getTwilightContext

functionBeginnerserverbrowserlive demo

Reads the engine's context outside React: store settings, language, location, route id, auth token and the data cache.

import { getTwilightContext, TwilightContext, TwilightLocation } from '@salla.sa/twilight-theme-engine/tanstack';

In plain words

Loaders, head functions and the root shell run where React hooks cannot be used. getTwilightContext() is how they ask "which language is this page in, what are the store's settings, where are we?".

On the server every request has its own private copy, opened by twilightMiddleware(). In the browser there is one copy for the whole page, kept up to date by the router.

It returns the values as they are now. Unlike a hook, it does not make a component render again when they change, so inside components prefer useTwilight() and the engine hooks.

Signature

function getTwilightContext(): TwilightContext

interface TwilightContext {
  queryClient: QueryClient;
  settings: StoreContext;        // getter: throws SettingsError until loaded
  locale: string;                // 'ar' until the root route has run
  dir: 'ltr' | 'rtl';            // from locale: ar, fa, he and ur are rtl
  routeId: string;               // a RouteId such as 'cart', or the raw TanStack id
  location: TwilightLocation;
  authToken: string | null;
  scope: ActiveScope | null;     // server only
  scopeId: string | null;        // scope?.id
  storeId: string | null;        // server only
  storeBase: string | null;      // server only
  versionId: string | null;      // server only
  requestHost: string | null;    // server only
  i18n: i18n;                    // created on first read when missing
  page?: PageContext;
  appsSnippets?: AppSnippet[];
  appsSettings?: AppSettings;
  extras?: Record<string, unknown>;
}

interface TwilightLocation<TSearch = Record<string, unknown>> {
  href: string;
  pathname: string;
  search: TSearch;
  searchStr: string;
  state: Record<string, unknown>;
  hash: string;
}

Try it live

The engine's context as this browser holds it right now, read with getTwilightContext().Try this: pick request addressing: every field is null here, even on localhost, because only the server middleware writes them.
Storefront canvas · en · LTR
Runs in the browser…
Controls
What a theme writes
import { getTwilightContext } from '@salla.sa/twilight-theme-engine/tanstack';

// A loader: runs on the server for the first page, in the browser after that.
export async function offersLoader() {
  const { locale, location } = getTwilightContext();
  return { locale, page: Number(location.search.page ?? 1) };
}

Example

app/routes/faq.tsx
// Declared in app/routes.ts: route('/faq', 'faq.tsx')
import { createFileRoute } from '@tanstack/react-router';
import { getTwilightContext } from '@salla.sa/twilight-theme-engine/tanstack';

export const Route = createFileRoute('/{-$locale}/faq')({
  loader: () => {
    const { settings, dir } = getTwilightContext();
    return { storeName: settings.store?.name ?? '', dir };
  },
  component: FaqPage,
});

function FaqPage() {
  const { storeName, dir } = Route.useLoaderData();
  return <h1 dir={dir}>Questions about {storeName}</h1>;
}

How it behaves

  • Server: one context per request, kept in AsyncLocalStorage and opened by twilightMiddleware(). Browser: one module-level context for the page.

  • The object it returns is a set of getters over that context, not a copy: keep it and read it later, and you get the values as they are then.

  • Who writes what: twilightMiddleware() the auth token, scope, store id, store base, version id and request host; createRouter() the queryClient; the root route's beforeLoad the settings, locale, i18n, location and auth token; the router the page, route id and location on each navigation.

  • __root.tsx reads it for <html lang dir> because the shell renders above TwilightProvider, where useTwilight() has no provider yet.

  • Values are also on useTwilight() inside components, and location on useLocation() from /providers.

Gotchas

  • settings is a getter that throws SettingsError while no settings are loaded, so ctx.settings?.store does not protect you: the throw happens when settings is read. In code that can run before the root route, such as a head function during hydration, catch SettingsError the way withHead() does.

  • On the server, outside a request, it throws [Twilight] No request context. Wrap with runWithTwilightContext(). Never call it at the top level of a module; call it inside loaders, beforeLoad, head functions or components.

  • In the browser storeId, storeBase, versionId, requestHost and scope are always null: only the server middleware writes them, and hydration copies settings, locale, i18n, auth token, location, page and route id (hydrateTwilightContext in src/tanstack/router.tsx). Read them on the server and return what the browser needs from a loader.

  • It is not reactive. Reading it while rendering gives the value once, and a later updateTwilightContext() does not render that component again. Inside components use useTwilight(), useRouteId() or useLocation().

  • packages/theme-engine/docs/router-context.md describes getRouteContext() and a /router subpath. Neither exists; getTwilightContext() from /tanstack is the function to use.

Related

Source and docs