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

notFound, redirect, unauthorized

functionAdvancedserverbrowserlive demo

Throw helpers for route loaders: stop and show the not-found page, send the visitor to another address, or refuse a guest.

import { notFound, redirect, unauthorized } from '@salla.sa/twilight-theme-engine/providers';

In plain words

A loader is the function that fetches a page's data before the page renders. Sometimes it has to stop: the product does not exist, the page moved, or the visitor must be signed in. Throw one of these and the router takes over: throw notFound(), throw redirect('/ar/cart'), throw unauthorized().

They are written without naming a router, so loader code reads the same whichever framework runs it.

Signature

let notFound: (message?: string) => never
let redirect: (path: string, opts?: { replace?: boolean }) => never
let unauthorized: (message?: string) => never

Try it live

Each helper throws, so a loader can stop. This page loaded the TanStack adapter, which changes what two of them throw.Try this: compare notFound with unauthorized: only unauthorized still throws the engine’s own error class.
Storefront canvas · en · LTR

TanStack adapter active: true

throw notFound(…): {…} 2 keys
kind: "TanStack notFound object"
value: {…} 2 keys
data: "Product not found"
isNotFound: true
Controls
What a theme writes
import { notFound } from '@salla.sa/twilight-theme-engine/providers';
import { product } from '@salla.sa/twilight-theme-engine/api/product';

export async function loader({ params }: { params: { id: string } }) {
  const found = await product.find(params.id).catch(() => null);
  if (!found) throw notFound('Product not found');
  return { product: found };
}

Example

app/routes/gift-card.$id.tsx (loader)
import { notFound, redirect } from '@salla.sa/twilight-theme-engine/providers';
import { product } from '@salla.sa/twilight-theme-engine/api/product';

export async function loader({ params }: { params: { id: string; locale?: string } }) {
  const found = await product.find(params.id).catch(() => null);
  if (!found) throw notFound('Gift card not found');
  // redirect() adds no locale: carry the current one (a single-language store has none).
  const prefix = params.locale ? `/${params.locale}` : '';
  if (found.type !== 'codes') throw redirect(`${prefix}/offers`);
  return { product: found };
}

How it behaves

  • They are let bindings that configureNavigation() replaces. Importing /tanstack, as every theme's app/router.tsx does, swaps in: notFound throws TanStack's notFound({ data: message }), redirect throws TanStack's redirect({ to: path, replace }), and unauthorized keeps throwing the engine's UnauthorizedError.

  • What the visitor sees: TanStack's not-found component for notFound, and for unauthorized the router's default error component, which renders a 401 ErrorPage without the home button (DefaultErrorComponent in src/tanstack/router.tsx). The engine's profile and settings loaders throw unauthorized() when there is no token.

  • Their return type is never, and they throw themselves. Writing throw notFound() makes the stop visible to readers and to TypeScript.

Gotchas

  • redirect uses the path exactly as given; unlike useNavigate(), no locale is added. On a multilingual store redirect('/cart') lands on a locale-less URL, and the locale wrapper sends it on to /ar/cart (the fallback locale), whatever language the visitor had. Build the path with the current locale.

  • Without the TanStack adapter (a unit test, or code that never imports /tanstack), redirect sets window.location in the browser before it throws RedirectError: a real navigation.

Related

Source and docs