Course
Loading UI
Next.js Mastery: From Fundamentals to Full-Stack
Unlock the power of Next.js with this comprehensive course! Starting with the basics, you’ll learn essential skills such as routing, data fetching, and styling. Progress through practical projects, including building your own React Notes app, to gain hands-on experience. Dive into the source code to understand the inner workings and core principles of Next.js. Perfect for developers with a basic knowledge of React and Node.js, this course ensures you’ll be ready to create high-performance full-stack applications efficiently. Join us and master Next.js, backed by industry experts and a supportive learning community.
Loading UI
Now that we have a grasp of the functionalities of page.js, layout.js, and template.js, let's consider some other important files. One such file is
loading.js
, provided by the App Router to display a loading UI.
This file utilizes React's Suspense API. For more on this, refer to the article React's Suspense documentation. When a route changes, it immediately shows a fallback UI and then displays the data once it's loaded.
<Suspense fallback={<Spinner />}> <ProfilePage /></Suspense>
A common question with Suspense is, "How do we control the closing of the fallback UI?"
Even React's documentation doesn't delve deeply into this. The logic is simple:
ProfilePage
throws a data-loading promise, which Suspense
captures and appends a then
function to. This function replaces the fallback UI. Once the data loads and the promise resolves, the then
function executes, updating the UI.
Let's see how to write
loading.js
. Create a loading.js
file in the dashboard
directory:
// app/dashboard/loading.jsexport default function DashboardLoading() { return ( <div className="flex items-center justify-center mt-40"> <h1 className="text-3xl">Loading...</h1> </div> );}
Here's the code for
page.js
at the same level:// app/dashboard/page.jsasync function getData() { await new Promise((resolve) => setTimeout(resolve, 3000)); return { message: 'Hello, Dashboard!', };}
export default async function Page() { const { message } = await getData();
return ( <div className="flex items-center justify-center mt-40"> <h1 className="text-3xl">{message}</h1> </div> );}
No additional code is needed to achieve the loading effect:
Playground
The key is that
page.js
exports an async function. The implementation of loading.js
wraps page.js
and its children in <Suspense>
. Because page.js
exports an async function, Suspense
captures the data-loading promise, allowing the loading component to close appropriately.
To implement a loading effect, exporting an async function is not the only method. You can also use React's
use
function. // /dashboard/page.jsimport { use } from 'react'
async function getData() { await new Promise((resolve) => setTimeout(resolve, 3000)) return { message: 'Hello, Dashboard!', }}
export default function Page() { const { message } = use(getData()) return ( <div className="flex items-center justify-center mt-40"> <h1 className="text-3xl">{message}</h1> </div> );}
This also achieves the loading effect:
If you want a specific loading effect for
/dashboard/about
, create another loading.js
in the about
directory.If a folder contains
layout.js
, template.js
, and loading.js
, what is their hierarchy?
This diagram clarifies their hierarchy: