Loading.jsx
flle: create a preloaderSuspense
I'm creating several videos about the main features of Next 13 (by Vercel) and in addition to the code I created some animations to simplify many concepts.
I have shared some tips on LinkedIn but, since after a while it's hard to find information, I have thoughts to share them on my blog too.
I have published a video course (in italian only) about Next JS (created with version 12 but updated to 13.x) and this content is part of that.
Usually a simple Next (v.13) application has at least the following files:
layout.tsx
: used to share components (and state) across multiple routespage.tsx
: one or more files that represent a single routeWhen we make fetch calls in a page created with NextJS we have to wait for the completion of the Promises before being able to view the rendered content.
import { User } from '@/model/user';
async function getData(): Promise<User[]> {
const res = await fetch('https://jsonplaceholder.typicode.com/users')
return res.json();
}
// This is a React Server Component
export default async function Demo() {
const data = await getData();
return (
<main>
<h1>Demo 2</h1>
{
data?.map(user => {
return <li key={user.id}>{user.name}</li>
})
}
</main>
)
}
The user experience suffers a lot so it would be appropriate to provide a loader and Next provides a very convenient way for doing so, that is, creating a loading.js
(or ts
) file in the same folder of the page/route.
This component may contain e.g. a spinner and will be displayed before page rendering. After that, it will automatically be destroyed.
After loading the page will then be rendered with all its client and server components
In the video above you can see how it works.
The problem is that we have to wait until ALL promises are resolved. There is a better way to display a separate loader for each component, but do you know how to this?
Anyway I will show you the solution (with code and another animation) it in the next recipe : )
Loading.jsx
flle: create a preloaderIn NextJs we can create a loading.jsx
file in the same folder of your route (page.js
/ts
) to show an instant loading state from the server while the content of your route segment loads.
The new content is automatically swapped in once rendering is complete.
(you can get more info about it and a small video in my previous post).
However, from a UI perspective, it's not the best technique we can use. If our page has to make two or more REST API calls we have to wait for all the promises to complete before seeing any element of the page.
This means that if our API calls take 3 seconds, we will have to wait that long to display anything.
An approach that I think is much better is to split the page into several server components, each of which will fetch the data. Wrapping these components with the Suspense component will get a better result:
we'll show a preloader of the entire page (thanks to the loading.js
file) which will be displayed only for the time necessary to render the static or client components, which will therefore be displayed almost immediately since they do not require further asynchronous fetches.
We will also display N preloaders for each of the server components which must instead make asynchronous calls, significantly improving the user experience and allowing the user to see the contents progressively, one after the other.
Probably your NextJS application will be organised into several pages/routes and each page can contain Client or Server components, or a mix of them .
A page could be totally static, with no need to invoke REST API and doesn't have interactive parts. In this case the page will be rendered on the server and will be totally static
Another page could request data, invoke a REST API and then render on the server. And this page may contain other server components within it that make other API calls or contains other static components (always pre-rendered on server)
Another section could be rendered on the server but contain:
However, we can also have a page created entirely client side and inside it have only client components, just like any application that uses Client Side Rendering, such as SINGLE PAGE APPLICATIONS (SPA).
In other words, the system is very flexible. In the next post I will show you some examples of Client and Server components
In NextJS we can create two types of components:
export default async function Page() {
const data = await getData();
return (<div>
<h1>Demo Next</h1>
<List data={data} />
</div>)
}
'use client'
export default function List(props) {
return (
<div>
{
props.data.map(item =>
<li
key={item.id}
onClick={doSomething}
>{item.name}</li>
)
}
</div>
)}
Code-wise, the main differences from the "normal" React component are:
The most obvious is that client components contain the "use client" directive at the top of the file
Server components can use async-await
and wait for promises to be resolved before rendering the template, thus without needing to use the useEffect
hook or tools like TanStack Query or the new React Router API in order to fetch data when the component is mount
Below are some indications provided by the Next team to use server or client components:
In #NextJS v.13 we can use async/await
to fetch data directly in the Server Components.
However, we may need to invoke two or more REST API in the same component, for example, to get data from multiple sources.
In the snippet below you can see a very simple example where a Server Component get "users" and "posts" through the use of two awaits ("getUsers" and "getPosts" are functions that use "fetch" and return a "Promise" ).
In this way, the operations will be done SEQUENTIALLY.
I mean that the second request will start only after the previous one has been completed. If each request takes 1 second, then we will have to wait 2 seconds before being able to render the component.
export default async function Page() {
const users = await getUsers();
const posts = await getPosts();
return (
<main>
// ... use data here ...
</main>
)
}
We can do better! 😎
How?
Fetching data in parallel, starting the calls "simultaneously" and, in this way, the result will arrive roughly within a second.
The calls are not really simultaneous but done one after the other in the server, so they run very fast
How can we do?
In the next snippet we have removed the two await
but we always invoke getUsers
and getPosts
functions which will start to fetch both data immediately.
However, we use a single "await" with "Promise.all" to stop the rendering of the component until all Promises are resolved.
export default async function Page() {
const usersData = getUsers();
const postsData = getPosts();
const [users, posts] = await Promise.all([
usersData, postsData
])
return (
<main>
// ... use data here ...
</main>
)
}
In both cases the component will render only after both fetches/promises have been resolved but in the second case we will cut the rendering time in half or nearly so.
Suspense
There is another strategy to reduce the rendering time of a NextJS page (and improve the UX) that needs to fetch data from multiple sources, therefore from different endpoints.
Instead of invoking several API into the same Server component (using different await
or Promise.all
, as we did before, and wait for both to complete) we can instead:
async/await
Suspense
, displaying a dedicated loader.import { Suspense } from 'react';
import { Posts } from '@/app/demo2/components/Posts';
import { Users } from '@/app/demo2/components/Users';
export default function Demo() {
return (
<main>
<Suspense fallback={<Spinner />}>
<Users />
</Suspense>
<Suspense fallback={<Spinner />}>
<Posts />
</Suspense>
</main>
)
}
import { getUsers } from '@/api/users.api';
export async function Users() {
const users = await getUsers(); // returns a promise
return (<div>
{
users?.map(...)
}
</div>)
}
For more tips you can follow me on my YouTube Channel or on LinkedIn
You can also read my original posts on LinkedIn (july/august 2023)