- Published on
How to Use an Animated Skeleton Loading Screen in Next.js
5 min read- Authors
- Name
- Kulwinder Uppal
- @Twitter/kulwindernzU
Summary
Everyone wants fast and optimized websites with minimal wait times. Loaders and spinners are commonly used to inform users that data is being fetched and will soon appear on the screen. In modern applications, skeleton screens have replaced traditional loaders, as they give the impression that data is loading quickly, and users get a sneak peek of how the final screen will look. Skeleton screens are particularly useful when server responses take some time. Many popular websites, such as YouTube, LinkedIn, and Spotify, have already adopted this approach.
In this tutorial, we won't reinvent the wheel by creating skeleton screens from scratch with CSS. Instead, we'll use the popular UI library Shadcn to display skeleton screens when dealing with slow API responses.
Prerequisites
Before getting started, make sure you have the following:
- Node Latest Version
- Editor (Visual Studio Code)
- Basic understanding of reactjs and hooks
Setting Up a Next.js and Tailwind CSS App
Create a Project
To create a Next.js project with Tailwind CSS, TypeScript, and ESLint, run the following npm command:
npx create-next-app@latest nextjs-tailwind-app --typescript --tailwind --eslint
Follow the installation process, answer the questions, and choose to use the src/
directory without the "App Router" option.
Run the CLI
Navigate to the project directory nextjs-tailwind-app
and install Shadcn UI by running:
cd nextjs-tailwind-app
npx shadcn-ui@latest init
Configure components.json
Open the src/pages/index.tsx
file in Visual Studio Code or your preferred code editor and remove everything inside the <main></main>
tags. We'll use a free API called JsonPlaceholder for this demonstration.
Below is the code that will fetch a list of users using the JsonPlaceholder fake API:
const getUsers = async () => {
const res = await fetch('https://jsonplaceholder.typicode.com/users')
if (!res.ok) throw new Error(res.statusText)
await new Promise(resolve => setTimeout(resolve, 3000));
const users = await res.json()
return users
}
We will utilize the React hooks useState
and useEffect
to store the response from getUsers() in a state variable called users.
const [users, setUsers] = useState<User[]>([])
useEffect(() => {
getUsers().then((users) => setUsers(users))
}, [])
We can display the list of users using the following JSX syntax. With the help of Tailwind CSS, we'll arrange 10 users in three rows, each with a four-column layout.
<main className="p-10 grid sm:grid-cols-2 md:grid-cols-4 lg:grid-cols-4 xl:grid-cols-4 gap-5">
{users.map((user) => (
<div key={user.id} className="grid-flow-col auto-cols-max gap-12">
<div>
<Image
src={`https://avatars.dicebear.com/api/avataaars/${user.name}.svg`}
alt={user.name}
width={96}
height={96}
className="object-cover rounded-md"
/>
</div>
<h2 className="text-sm font-bold p-6 mb-3 bg-slate-100">{user.name}</h2>
<div className="text-gray-500 pb-8 mb-4 bg-slate-100">{user.email}</div>
<div className='bg-slate-100 p-4'>{user.address.street}, {user.address.suite}, {user.address.city} - {user.address.zipcode}</div>
</div>
))}
</main>
Running the Project
To start the project, run:
npm run dev
You should now see 10 users on the screen, each with their profile image, name, email, and address.
Introducing the Skeleton Screen
Since the response from our fake API is expected to be quick, we'll need to simulate a slow API response by adding a delay to the getUsers() function.
await new Promise(resolve => setTimeout(resolve, 3000));
Upon refreshing the screen, you'll notice a blank (white) screen for three seconds before the fetched users are displayed.
Implementing the Skeleton Screen
Installing the Skeleton Component To install the Skeleton component, run the following command:
npx shadcn-ui@latest add skeleton
This will create a nested directory structure inside the src
directory and place the skeleton component file inside src/components/ui/skeleton.tsx
.
Next, create a new file src/pages/loading.tsx
inside the pages
directory.
Copy and paste the following JSX code into the loading.tsx
file:
import { Skeleton } from "@/components/ui/skeleton"
export default function Loading() {
return (
<main className="p-10 grid sm:grid-cols-2 md:grid-cols-4 lg:grid-cols-4 xl:grid-cols-4 gap-5">
{Array.from({length: 12},(_, i) => i + 1).map((id) => (
<div key={id} className="grid-flow-col auto-cols-max gap-12">
<Skeleton className="object-none w-32 h-32 rounded-full custom-position bg-gray-200" />
<Skeleton className="h-10 w-full p-6 mb-4 bg-slate-100" />
<Skeleton className="h-10 w-full p-8 mb-4 bg-slate-100" />
<Skeleton className='h-10 w-full p-10 mb-4 bg-slate-100' />
</div>
)
)}
</main>
)
}
The final step is to display the loading screens before rendering the user list:
return (
users.length <= 0 ? <Loading /> : (
<main ...
</main>
)
)
Your index.tsx
file should now look like this:
import { useState, useEffect } from 'react'
import Image from 'next/image'
import Loading from './loading'
type User = {
id: number
name: string
email: string,
address: {
street: string,
suite: string,
city: string,
zipcode: string,
}
}
const getUsers = async () => {
const res = await fetch('https://jsonplaceholder.typicode.com/users')
if (!res.ok) throw new Error(res.statusText)
await new Promise(resolve => setTimeout(resolve, 3000));
const users = await res.json()
return users
}
export default function Home() {
const [users, setUsers] = useState<User[]>([])
useEffect(() => {
getUsers().then((users) => setUsers(users))
}, [])
return (
users.length <= 0 ? <Loading /> : (
<main className="p-10 grid sm:grid-cols-2 md:grid-cols-4 lg:grid-cols-4 xl:grid-cols-4 gap-5">
{users.map((user) => (
<div key={user.id} className="grid-flow-col auto-cols-max gap-12">
<div>
<Image
src={`https://avatars.dicebear.com/api/avataaars/${user.name}.svg`}
alt={user.name}
width={96}
height={96}
className="object-cover rounded-md"
/>
</div>
<h2 className="text-sm font-bold p-6 mb-3 bg-slate-100">{user.name}</h2>
<div className="text-gray-500 pb-8 mb-4 bg-slate-100">{user.email}</div>
<div className='bg-slate-100 p-4'>{user.address.street}, {user.address.suite}, {user.address.city} - {user.address.zipcode}</div>
</div>
))}
</main>
)
)
}
Testing Your App
Refresh the browser, and you'll see the skeleton screens displayed before the user data loads.
This implementation improves user experience by providing visual feedback during loading times.