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

Page skeletons

componentBeginnerserverbrowserlive demo

Loading placeholders shaped like the home, product, cart, blog and account pages, shown while the next page loads its data.

import { PageSkeleton, ProductDetailSkeleton, BlogSkeleton, CartSkeleton, HomeSkeleton, CustomerPageSkeleton, CustomerLayoutSkeleton, PageSkeletonProps } from '@salla.sa/twilight-theme-engine/skeleton';

In plain words

When a shopper clicks a link, the next page's data takes a moment to arrive. A skeleton fills that moment: grey shapes where the title, pictures and prices will appear, so the store feels quick instead of frozen.

The engine has one for each kind of page, and its generated routes already use them. You pick one when you write a route of your own.

Signature

const PageSkeleton: MemoExoticComponent<(props: PageSkeletonProps) => JSX.Element>
const ProductDetailSkeleton: MemoExoticComponent<() => JSX.Element>
const BlogSkeleton: MemoExoticComponent<() => JSX.Element>
const CartSkeleton: MemoExoticComponent<() => JSX.Element>
const HomeSkeleton: MemoExoticComponent<() => JSX.Element>
const CustomerPageSkeleton: MemoExoticComponent<() => JSX.Element>
const CustomerLayoutSkeleton: MemoExoticComponent<() => JSX.Element>

interface PageSkeletonProps {
  children?: ReactNode;   // rendered instead of the default skeleton
}

Try it live

The loading placeholders the engine puts on its routes. Each is drawn from the real storefront classes, so it has the shape of the page it stands in for.Try this: switch to CartSkeleton, then open the real cart in Built-in pages and compare.
Storefront canvas · en · LTR
Shown while loading: every route without its own (the router default).
Controls
What a theme writes
import { createFileRoute } from '@tanstack/react-router';
import { PageSkeleton } from '@salla.sa/twilight-theme-engine/skeleton';
import { loadMyPage, MyPage } from '../components/MyPage';

export const Route = createFileRoute('/{-$locale}/my-page')({
  loader: () => loadMyPage(),
  pendingComponent: () => <PageSkeleton />,
  component: MyPage,
});

Example

app/routes/cart.tsx
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';

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 <Cart.Component {...data} />;
}

How it behaves

  • They come from @salla.sa/twilight-components-react, except HomeSkeleton, which is the engine's own. All are eager and memoised, so they render at once with no <Suspense>: that is the point of a loading state.

  • createRouter() shows PageSkeleton for any route without its own pendingComponent; its defaultPendingComponent option replaces it, and false turns it off.

  • Generated routes use HomeSkeleton (home), ProductDetailSkeleton (product), BlogSkeleton (blog, article, author, category and tag pages), CartSkeleton (cart) and CustomerPageSkeleton (each account page). The account layout shows CustomerLayoutSkeleton, unless a component is registered under account:layout-pending.

  • A pending component shows when a navigation's loader takes longer than defaultPendingMs (100 ms), and then stays at least defaultPendingMinMs (200 ms).

  • Their s-skeleton-* classes are styled by Salla's Tailwind plugin (@salla.sa/twilight-tailwind-theme, loaded by the theme's tailwind.config), so each has the grid of the page it stands in for. HomeSkeleton uses plain Tailwind utilities.

  • All but CartSkeleton and HomeSkeleton are also exported from @salla.sa/twilight-theme-engine/components/common.

Gotchas

  • The example is the reference theme's app/routes/cart.tsx, which the engine generates with // @auto-generated as its first line. A file that starts with that line is rewritten on every dev and build, so a pendingComponent you change there is lost. Delete that first line to keep your version.

  • PageSkeleton with children renders only those children. It does not add them to the default skeleton.

  • Without the Salla Tailwind plugin in the theme build, the s-skeleton-* classes have no styles: no grey, no pulse and no size, so nothing visible appears while the page loads.

Related

Source and docs