I'm looking for part time work! check out my portfolio on how to contact me
With the new release of Next.js 13 came with new paradigms and concepts. One prevalent concept that react and next.js introduced
are server components, which are components that are rendered on the server. This allows for faster page loads and better SEO.
This is a great concept where you can do asynchronous tasks on a react component such as data fetching. This means you won't need
useEffect
to get data from an API; it is considered an anti-pattern in react when it comes to fetching data on the client with
useEffect
because it can cause some expensive problems on the long run if you are not careful with it.
One of the problems that the react documentation explained are race conditions which are explained on the link.
It is much better for SEO and performance to fetch data on the server, and Next.js 13 makes it easier to do so. In this article
we will be going over how to fetch data with the new Next.js 13 App Router, and some tips and tricks.
Solution 1: Fetching data with fetch
and React Server Components
This is as simple as it gets, create an async component, fetch data with fetch
on the top level then access the data on the
template.
// app/page.jsx
export default async function Page() {
const res = await fetch("https://jsonplaceholder.typicode.com/posts");
const posts = await res.json();
return (
<div>
<h1>Posts</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
}
That's it! It will fetch the data on the server and serve an html page with the data already fetched. If you want to revalidate or refetch
the data, Next.js extends the fetch
where the framework allows you to configure the caching and revalidation behavior for each request
on the server.
In the previous example, we can further improve the fetch api by adding the revalidate
property to the response object and doing error handling.
Let's say we want to revalidate the data every 1 hour.
// app/page.jsx
export default async function Page() {
const res = await fetch("https://jsonplaceholder.typicode.com/posts", {
next: { revalidate: 3600 }, // Will revalidate every 1 hour
});
const posts = await res.json();
return (
<div>
<h1>Posts</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
}
After that, we can add a throw statement to throw an error if the response status is not 200.
// app/page.jsx
export default async function Page() {
const res = await fetch("https://jsonplaceholder.typicode.com/posts", {
next: { revalidate: 3600 }, // Will revalidate every 1 hour
});
if (!res.ok) {
// This will activate the closest `error.js` Error Boundary.
throw new Error(`Failed to fetch the data`)
}
const posts = await res.json();
return (
<div>
<h1>Posts</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
)
}
But what if you really need to fetch data on the client? This is where the second solution comes in.
Solution 2: Fetching data in the client with third party libraries
Like I said, it is better to fetch data on the server for SEO and performance because the data that will be fetched will also be included in the HTML.
But if client side data fetching is needed, consider using a third party library first like SWR and React Query to avoid using useEffect
This is because of the edge cases that these library have are already included like deduplicating requests, caching responses,
avoiding network waterfalls, refetching, mutation, etc.
Let's take a look on what it looks like when you want to fetch
data with SWR
SWR is a data fetching library created by vercel which is a much
more simpler solution compared to react query.
Let's say we want to fetch data in a client component.
// components/profile.tsx
import { getProfileById } from '@/lib/actions'; // Example import
export function Profile({id}: {id: string}) {
const { data, error, isLoading } = useSWR([`/api/profile/`, id], ([url, userId]) => getProfileById(url, userId))
if (error) return <div>failed to load</div>
if (isLoading) return <div>Loading...</div>
return <div>hello, {data?.name}</name>
}
It looks similar, but the difference is that it is fetching directly in the browser. If you look at the difference between the network requests in your devtools you would see the request
being made.
Improving Data Fetching with Suspense
This can be further improved by using the Suspense component. Suspense in react is a component that replaces a fallback component until the children that is wrapped around it has finished loading.
We can do this by the following example:
// components/profile.tsx
'use client'
import { getProfileById } from '@/lib/actions'; // Example import
export function Profile({ id }: { id: string }) {
const { data, error, isLoading } = useSWR([`/api/profile/`, id], ([url, userId]) => getProfileById(url, userId), { suspense: true })
return <div>hello, {data.name}</name>
}
then on, app/page.tsx
:
// app/page.tsx
'use client'
import { Suspense } from 'react'
import { Skeleton } from '@/components/ui/skeleton' // Example
import { Profile } from '@/components/profile'
import { ErrorBoundary } from 'react-error-boundary' // npm install react-error-boundary
export default function Page() {
return (
<ErrorBoundary fallback={<h2>Could not fetch profile</h2>}>
<Suspense fallback={<Skeleton />}>
<Profile />
</Suspense>
</ErrorBoundary>
)
}
It looks much cleaner and doesn't actually need to use optional chaining operator and conditional checks.
IMPORTANT NOTE
React Suspense is still in experimental when it comes to data
fetching but the react team is working on how to properly
handle suspense when it comes to data fetching.React Suspense does not detect when data is fetched inside > an Effect or event handler.
Tips and Tricks
Here are some good tips and tricks when it comes to data fetching in react.
- Avoid data fetching in the client as much as possible.
- Avoid using
useEffect
for data fetching, use an opinionated data fetching library to use the full potential of what react can offer when handling async data. - In my opinion it is considered bad practice to add
'use client'
in page.tsx or layout.tsx because ideally these should be rendered on the server. - When dealing with search fields or input data, it is good to consider using debounce or throttling techniques to reduce the amount of requests and to avoid api abuse.
Conclusion
In this article, we learned how we can fetch data in the new
Next.js App Router in the server and the client. We also had
some quick discussion react suspense and how we can use it to
display loading data.
I hope you learned something on this article and apply it on
your current and new projects.
Follow me on social media!
I am active on the following social media platforms.
Top comments (0)