An instant loading state is fallback UI shown immediately after navigation. For a better user experience, use meaningful placeholders like skeletons, spinners, or partial previews (e.g., cover photo, title) to signal responsiveness.
In sequential fetching, nested components each fetch their own data independently, which can delay rendering. This pattern is useful when one component depends on the result of another.
For example, <Playlists> waits for <Artist> to finish before fetching:
// app/artist/[username]/page.tsx
export default async function Page({ params }) {
const { username } = await params
const artist = await getArtist(username)
return (
<>
<h1>{artist.name}</h1>
<Suspense fallback={<div>Loading...</div>}>
<Playlists artistID={artist.id} />
</Suspense>
</>
)
}
async function Playlists({ artistID }) {
const playlists = await getArtistPlaylists(artistID)
return (
<ul>
{playlists.map((playlist) => (
<li key={playlist.id}>{playlist.name}</li>
))}
</ul>
)
}Parallel fetching starts multiple requests simultaneously. In Next.js, layouts and pages are rendered in parallel by default, but inside a component, sequential await calls can still block rendering.
Use Promise.all to fetch data concurrently:
// app/artist/[username]/page.tsx
export default async function Page({ params }) {
const { username } = await params
const artistData = getArtist(username)
const albumsData = getAlbums(username)
const [artist, albums] = await Promise.all([artistData, albumsData])
return (
<>
<h1>{artist.name}</h1>
<Albums list={albums} />
</>
)
}Note: If one request fails in Promise.all, the entire operation fails. Use Promise.allSettled for safer handling.
Preload data by calling a utility function before blocking requests. This ensures data is ready when the component renders:
// app/item/[id]/page.tsx
export default async function Page({ params }) {
const { id } = await params
preload(id)
const isAvailable = await checkIsAvailable()
return isAvailable ? <Item id={id} /> : null
}
export const preload = (id) => {
void getItem(id)
}
export async function Item({ id }) {
const result = await getItem(id)
// ...
}Use React’s cache and the server-only package to cache server-side fetch functions:
// utils/get-item.ts
import { cache } from 'react'
import 'server-only'
import { getItem } from '@/lib/data'
export const preload = (id) => {
void getItem(id)
}
export const getItem = cache(async (id) => {
// ...
})By designing meaningful loading states, using parallel data fetching, and preloading smartly, you can deliver fast, responsive experiences in Next.js. Suspense boundaries and server-side caching help optimize rendering and reduce wait times for users.