← Back
Edit Post
Title
Description
Co-locate skeletons with components, match layout structure, prevent CLS.
Content
Markdown supported
# Skeleton Loading Spinners say "something is happening." **Skeletons say "this is what's coming."** They preview the content structure, reducing perceived loading time and preventing layout shift when data arrives. A spinner offers no information about what will appear. A skeleton shows the shape of content—users mentally prepare for what's coming, and the transition from placeholder to real content feels natural rather than jarring. ## The Co-location Pattern Export skeletons alongside their components. When you change a component's layout, the skeleton is right there to update: ```tsx // PostList.tsx export async function PostList({ searchParams }) { const posts = await getPosts(searchParams); return ( <div className="space-y-4"> {posts.map(post => ( <Card key={post.slug}> <CardTitle>{post.title}</CardTitle> <p>{post.description}</p> </Card> ))} </div> ); } // Co-located skeleton in the same file export function PostListSkeleton() { return ( <div className="space-y-4"> {[1, 2, 3].map(i => ( <Card key={i}> <Skeleton className="h-6 w-48" /> <Skeleton className="h-4 w-full" /> </Card> ))} </div> ); } ``` ## Match the Layout Structure The skeleton should mirror the real component's structure exactly: - Same spacing (`space-y-4`) - Same container types (`Card`, `CardHeader`) - Same approximate dimensions When data loads, content replaces skeleton with zero layout shift. ## Using with Suspense Import both the component and its skeleton: ```tsx import { PostList, PostListSkeleton } from './_components/PostList'; export default function DashboardPage({ searchParams }) { return ( <Suspense fallback={<PostListSkeleton />}> <PostList searchParams={searchParams} /> </Suspense> ); } ``` ## Skeleton Design Tips | Element | Skeleton | |---------|----------| | Title | Rectangle, ~60% width | | Date/metadata | Shorter rectangle, ~30% width | | Description | Full width, shorter height | | Avatar | Circle | | Button | Rectangle with rounded corners | Use subtle animation (`animate-pulse`) to indicate loading, but keep it gentle—aggressive animation is distracting.
Published
Save Changes
Cancel