Show Loading at the Route Segment in Next.js 15
React's Suspense feature, especially powerful in Next.js, enables seamless loading states in applications. This approach is ideal when components perform asynchronous tasks, such as fetching data. Suspense renders a loading state whenever a component's promise is pending, only displaying the final content once it’s fully ready.
How Suspense Works in React
Suspense operates by showing a loading fallback only when the child component is in a "suspended" state. For this to happen, the component itself must return a promise. While the promise is unresolved, Suspense keeps the user engaged with the fallback UI, preventing content flickering or blank screens during async operations.
For example, when we have an async component in Next.js, any delay caused by data fetching or processing will trigger Suspense to show the fallback state. Here’s how you can set up a simple Suspense wrapper:
// SuspenseExample.tsx
import React, { Suspense } from "react";
import DataFetchingComponent from "./DataFetchingComponent";
export default function SuspenseExample() {
return (
<Suspense fallback={<div>Loading...</div>}>
<DataFetchingComponent />
</Suspense>
);
}
Addressing Slow Page Loads in Next.js
When using async functions for data fetching, there’s often a noticeable delay in rendering, impacting both user experience and SEO. Slow responses or waiting on multiple promises can leave the page blank for several seconds. By wrapping these components in Suspense, we ensure that the loading state appears instantly, enhancing user perception of page responsiveness.
Consider this Next.js code that simulates a 5-second delay:
// pages/slowPage.tsx
export default async function SlowPage() {
await new Promise((resolve) => setTimeout(resolve, 5000));
return <div>Data Loaded</div>;
}
Without Suspense, users would see a blank screen for the duration. This approach, however, compromises SEO due to high time-to-first-byte (TTFB) and page rendering times.
Using the loading.tsx
File for Better Loading Feedback
In Next.js, we can take advantage of the loading.tsx
file, a special convention that shows a loading indicator during page loads. This approach is similar to Suspense but works seamlessly at the page level, ensuring each page has its fallback while data loads in the background. Creating a loading.tsx
file next to the page component provides a smoother experience.
Here’s an example:
// app/slowPage/loading.tsx
export default function Loading() {
return <div>Loading content, please wait...</div>;
}
When a page’s async data takes time to load, Next.js automatically displays this loading.tsx
fallback until the page is ready.
Building Skeleton Loaders for a Better User Experience
Skeleton loaders provide a preview of the content structure, reducing content shifts as the actual data loads in. This minimizes the negative SEO impacts of content layout shifts, which can happen with simple "Loading..." text.
Using a skeleton loader in Next.js can improve the perceived load time, as it visually prepares users for the incoming content:
// SkeletonLoading.tsx
import React from "react";
import "./SkeletonLoading.css";
export default function SkeletonLoading() {
return (
<div className="skeleton">
<div className="skeleton-header"></div>
<div className="skeleton-content"></div>
</div>
);
}
This skeleton setup gives users a visual sense of the layout, reducing jarring shifts and improving the overall user experience.
Understanding Suspended Mode Triggers in Next.js
Several common use cases put a Next.js page into "suspended mode," including API requests and database queries. When Next.js encounters async functions at the page level, it wraps the content in a Suspense-like structure to render a loading state. Here’s an example of a database query that could trigger this behavior:
// Example of async data fetching
async function fetchDataFromDatabase() {
const data = await db.query("SELECT * FROM example_table");
return data;
}
export default async function ExamplePage() {
const data = await fetchDataFromDatabase();
return <div>{JSON.stringify(data)}</div>;
}
While data loads, Next.js automatically invokes the loading.tsx
file to handle any delays, improving the load experience for users.
Final Thoughts on Using Suspense and Loading States in Next.js
Using Suspense effectively with loading.tsx
in Next.js helps ensure fast, responsive loading feedback. This setup improves SEO by reducing TTFB and avoids exposing users to blank screens or content shifts. As a best practice, especially when data fetching is involved, integrating Suspense or skeleton loaders is key to optimizing the user interface and keeping the experience smooth across different network conditions.