Skeletons & Loading States
Relevant source files
The following files were used as context for generating this wiki page:
The skeleton and loading state system provides a seamless user experience by displaying placeholder UIs while page components are being lazy-loaded. This approach, integrated with React Suspense, prevents jarring content shifts and gives an immediate impression of speed. The system is built on a set of composable skeleton components that mimic the layout of the actual pages.
The core of this system is the PageSkeleton component, which provides a consistent frame including a navigation bar and footer skeleton. Specific content skeletons, like HomeSkeleton or the generic PageContentSkeleton, are then rendered within this frame, corresponding to the page being loaded. This ensures that the overall page structure is visible instantly, with content placeholders being replaced by the real content once it's available.
Architecture
The loading state architecture is centered around composition and React's Suspense feature. A top-level PageSkeleton component wraps the main content area, providing a consistent layout shell. This shell is then populated by a more specific content skeleton.
Sources: src/app/App.tsx:5-9, src/shared/ui/PageSkeleton/PageSkeleton.tsx:7-29
Component Hierarchy
The skeleton components are structured hierarchically. PageSkeleton is the top-level container, which includes NavbarSkeleton and FooterSkeleton. It accepts a children prop, which is typically a content-specific skeleton like PageContentSkeleton.
The following diagram illustrates the composition of skeleton components.
Loading Sequence Flow
When a user navigates to a lazy-loaded route, React Suspense intercepts the render and displays the provided fallback UI. In this application, the fallback is a PageSkeleton containing a PageContentSkeleton. Once the lazy-loaded page component finishes loading, Suspense automatically swaps the skeleton UI with the actual page content.
This diagram shows the sequence of events during a page transition.
Sources: src/app/App.tsx:22-25, src/app/App.tsx:39-44
Core Components
The system is comprised of several key skeleton components, each serving a specific purpose.
| Component | Purpose | File Path |
|---|---|---|
PageSkeleton | A high-level wrapper providing the main page structure (nav, content, footer). | src/shared/ui/PageSkeleton/PageSkeleton.tsx |
HomeSkeleton | A detailed skeleton specifically for the Home page's unique layout. | src/shared/ui/skeletons/HomeSkeleton.tsx |
PageContentSkeleton | A generic skeleton for standard content pages with a title and paragraphs. | src/shared/ui/skeletons/PageContentSkeleton.tsx |
NavbarSkeleton | A placeholder for the main navigation bar. | src/shared/ui/skeletons/NavbarSkeleton.tsx |
FooterSkeleton | A placeholder for the page footer. | src/shared/ui/skeletons/FooterSkeleton.tsx |
Sources: src/shared/ui/skeletons/index.ts, src/shared/ui/PageSkeleton/PageSkeleton.tsx, src/shared/ui/skeletons/HomeSkeleton.tsx
PageSkeleton
This is the main layout skeleton. It renders NavbarSkeleton and FooterSkeleton and provides a main content area where child components (typically another skeleton) are rendered. If no children are provided, it renders a generic fallback hero shape.
// src/shared/ui/PageSkeleton/PageSkeleton.tsx:7-29
const PageSkeleton: React.FC<PageSkeletonProps> = ({ children }) => {
return (
<div className="flex min-h-screen flex-col bg-bg-default">
<NavbarSkeleton />
<main className="grow">
{children ? (
children
) : (
<div className="min-h-screen animate-pulse bg-bg-default overflow-hidden relative pt-20">
{/* Fallback generic hero shape if no children provided */}
<div className="mx-auto max-w-7xl px-6 pt-20">
<div className="h-24 w-24 rounded-2xl bg-bg-subtle mb-12"></div>
<div className="h-16 w-3/4 rounded-2xl bg-bg-subtle mb-6"></div>
<div className="h-8 w-1/2 rounded-2xl bg-bg-subtle mb-12"></div>
</div>
</div>
)}
</main>
<FooterSkeleton />
</div>
);
};
Sources: src/shared/ui/PageSkeleton/PageSkeleton.tsx:7-29
HomeSkeleton
This component provides a high-fidelity skeleton for the home page, which has a complex layout. It includes placeholders for the hero section (logo, headline, subtext, CTAs) and the bento grid section. This level of detail ensures the loading state closely matches the final UI.
// src/shared/ui/skeletons/HomeSkeleton.tsx:5-11
const HomeSkeleton = () => {
return (
<div className="w-full animate-pulse">
{/* Hero Section Skeleton */}
<section className="relative h-screen flex flex-col justify-center pt-32 pb-20">
<Container>
{/* ... hero skeleton elements ... */}
</Container>
</section>
{/* Bento Section Skeleton */}
<section className="min-h-screen lg:h-screen flex flex-col justify-center mx-auto max-w-7xl px-6 w-full py-20 lg:py-0">
<div className="grid grid-cols-1 grid-rows-10 md:grid-cols-6 md:grid-rows-4 h-auto md:min-h-175 md:h-[calc(100vh-12rem)] md:max-h-225 gap-4 w-full">
{/* ... bento grid skeleton elements ... */}
</div>
</section>
</div>
);
};
Sources: src/shared/ui/skeletons/HomeSkeleton.tsx:5-70
PageContentSkeleton
This is a generic skeleton used for simple content pages. It renders placeholders for a page title and several paragraphs of text. It is used as the default fallback for most lazy-loaded routes in App.tsx.
// src/shared/ui/skeletons/PageContentSkeleton.tsx:4-15
const PageContentSkeleton = () => {
return (
<div className="min-h-screen pt-32 pb-12 animate-pulse">
<Container>
{/* Title */}
<div className="h-10 w-1/3 rounded-lg bg-bg-subtle mb-8"></div>
{/* Paragraphs */}
<div className="space-y-4 max-w-3xl">
<div className="h-4 w-full rounded bg-bg-subtle"></div>
<div className="h-4 w-5/6 rounded bg-bg-subtle"></div>
{/* ... more paragraphs ... */}
</div>
</Container>
</div>
);
};
Sources: src/shared/ui/skeletons/PageContentSkeleton.tsx:4-28
Integration
The skeleton components are integrated into the main application entry point, App.tsx, using React.Suspense to handle the loading states of lazy-loaded pages.
A fallback element is defined once and reused for all routes. This fallback consists of the PageSkeleton wrapping the PageContentSkeleton.
// src/app/App.tsx:22-27
const App = () => {
const fallback = (
<PageSkeleton>
<PageContentSkeleton />
</PageSkeleton>
);
//...
Each lazy-loaded route is then wrapped in a Suspense component, which is configured to use this fallback UI while waiting for the page component to load.
// src/app/App.tsx:39-44
<Route
path="/projects"
element={
<Suspense fallback={fallback}>
<Projects />
</Suspense>
}
/>
This pattern is repeated for all pages, providing a consistent loading experience across the entire application.
Sources: src/app/App.tsx:22-27, 39-44
Summary
The project's skeleton system effectively enhances user experience by providing immediate visual feedback during navigation and initial load. By using a composable architecture with PageSkeleton as a base and specific content skeletons for different pages, it creates high-fidelity placeholders that closely mimic the final page layout. The integration with React Suspense and lazy makes the implementation clean, declarative, and efficient, ensuring a smooth transition from loading state to fully rendered content.