Skeletons
Placeholder screens shown while a page's data loads, and how to choose or replace them.
A skeleton is a grey placeholder in the shape of the page. The engine shows one while a route's loader fetches data, so the screen does not freeze after a click. You rarely render one yourself: you choose which one a route shows.
Example
import { createFileRoute } from '@tanstack/react-router';
import { CartSkeleton } from '@salla.sa/twilight-theme-engine/skeleton';
export const Route = createFileRoute('/{-$locale}/cart')({
pendingComponent: () => <CartSkeleton />,
});Built-in skeletons
All come from @salla.sa/twilight-theme-engine/skeleton.
| Component | Looks like |
|---|---|
PageSkeleton | A generic page: breadcrumb, title and a product grid. Used by every route unless it names its own. |
ProductDetailSkeleton | A product page: gallery, details and the add-to-cart form. |
CartSkeleton | The cart. |
BlogSkeleton | A blog list or article. |
HomeSkeleton | The home page. |
CustomerPageSkeleton | A page inside the customer account. |
CustomerLayoutSkeleton | The customer account frame. |
BreadcrumbSkeleton | The breadcrumb row. A building block. |
ProductCardSkeleton, ProductGridSkeleton | One product card, or a grid of them. Building blocks. |
PageSkeleton takes optional children. When you pass children, it renders them instead of the default layout, so you can keep the same entry point and supply your own shape.
When a skeleton appears
The router decides, not the skeleton. Two timings apply, and the engine sets them for you:
| Option | Default | Meaning |
|---|---|---|
defaultPendingMs | 100 | Wait this long (ms) before showing a skeleton. Fast loads show none. |
defaultPendingMinMs | 200 | Once shown, keep it at least this long (ms) so it does not flash. |
Change them, or the default skeleton, in the createRouter call inside getRouter() in app/router.tsx:
import { createRouter } from '@salla.sa/twilight-theme-engine/tanstack';
import { PageSkeleton } from '@salla.sa/twilight-theme-engine/skeleton';
// inside getRouter()
const router = createRouter(routeTree, {
defaultPendingComponent: () => <PageSkeleton />,
defaultPendingMs: 150,
defaultPendingMinMs: 200,
});Gotchas
- A skeleton shows only if the loader takes longer than
defaultPendingMs. A cached or quick page goes straight to its content. - A route's own
pendingComponentwins over the default one. - Make a skeleton close to the real layout's height. A very different size makes the page jump when the content arrives.
Next
- Routes and pages covers
pendingComponentin a route. - Performance