Course
Best Practices
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.
Best Practices
Here are some recommendations and best practices for data fetching in React and Next.js:
1. Fetch Data on the Server Whenever Possible
Fetching data on the server has several advantages, such as:
- Direct access to backend resources (e.g., databases)
- Preventing sensitive information leakage
- Reducing communication between the client and server, speeding up response times
- ...
2. Fetch Data Where It Is Needed
If multiple components in the component tree use the same data, you don't need to fetch it globally and pass it down via props. Instead, fetch the data where it is needed using fetch or React cache. Don't worry about performance issues caused by multiple requests, as fetch requests are automatically memoized. This also applies to layouts, as data cannot be passed between parent and child layouts.
3. Use Streaming When Appropriate
Streaming and Suspense are React features that allow incremental content transmission and progressive UI rendering. A page can render partial content directly while displaying a loading state for parts that are still fetching data. This means users can interact with the page before it fully loads.
Note: For detailed explanations of Suspense and Streaming, refer to the section Rendering: Suspense and Streaming.
4. Serial Data Fetching
There are two data fetching patterns in React components: serial and parallel.
Serial data fetching occurs when data requests depend on each other, creating a waterfall structure, which can sometimes be necessary but leads to longer load times.
Parallel data fetching occurs when requests happen simultaneously, reducing the total data loading time.
Let's look at an example of serial data fetching:
// app/artist/page.js
async function Playlists({ artistID }) { // Wait for playlists data const playlists = await getArtistPlaylists(artistID);
return ( <ul> {playlists.map((playlist) => ( <li key={playlist.id}>{playlist.name}</li> ))} </ul> );}
export default async function Page({ params: { username } }) { // Wait for artist data const artist = await getArtist(username);
return ( <> <h1>{artist.name}</h1> <Suspense fallback={<div>Loading...</div>}> <Playlists artistID={artist.id} /> </Suspense> </> );}
In this example, the Playlists component starts fetching data only after the Artist component has fetched its data because Playlists depends on the artistId prop. This is necessary since you need to know which artist to fetch playlists for.
To prevent serial data fetching:
- Use loading.js or React's <Suspense> component to show an immediate loading state and avoid blocking the entire route.
- Parallelize data fetching whenever possible or use preloading techniques.
5. Parallel Data Fetching
To achieve parallel data fetching, define the requests outside the component using the data and then call them inside the component:
import Albums from './albums';
// Define functions outside the componentasync function getArtist(username) { const res = await fetch(`https://api.example.com/artist/${username}`); return res.json();}
async function getArtistAlbums(username) { const res = await fetch(`https://api.example.com/artist/${username}/albums`); return res.json();}
export default async function Page({ params: { username } }) { // Call functions inside the component (parallel fetching) const artistData = getArtist(username); const albumsData = getArtistAlbums(username);
// Wait for all promises to resolve const [artist, albums] = await Promise.all([artistData, albumsData]);
return ( <> <h1>{artist.name}</h1> <Albums list={albums}></Albums> </> );}
In this example, the getArtist and getArtistAlbums functions are defined outside the Page component and called inside it. The user has to wait until both promises resolve to see the result.
To enhance the user experience, use the Suspense component to split the rendering work and display partial results as soon as possible.
6. Preloading Data
Another way to prevent serial requests is to use preloading:
// app/article/[id]/page.jsimport Article, { preload, checkIsAvailable } from './components/Article';
export default async function Page({ params: { id } }) { // Preload article data preload(id); // Perform another async task (e.g., checking access permissions) const isAvailable = await checkIsAvailable();
return isAvailable ? <Article id={id} /> : null;}
In the preload function, use React's cache function:
// components/Article.jsimport { getArticle } from '@/utils/get-article';import { cache } from 'react';
export const getArticle = cache(async (id) => { // ...});
export const preload = (id) => { void getArticle(id);};
export const checkIsAvailable = (id) => { // ...};
export default async function Article({ id }) { const result = await getArticle(id); // ...}
7. Using React Cache, server-only, and Preloading
Combine cache functions, preloading, and the server-only package to create a data fetching utility function usable across the entire application:
// utils/get-article.jsimport { cache } from 'react';import 'server-only';
export const preloadArticle = (id) => { void getArticle(id);};
export const getArticle = cache(async (id) => { // ...});
This way, you can preload data, cache results, and ensure data fetching happens only on the server. Layouts, pages, and components can all use utils/get-article.js.
Note: For detailed information on the preload function, server-only, and cache features, refer to the article: When Next.js encounters frequent repeated database operations, remember to use React's cache function.
Conclusion
Congratulations on completing this section!
This section introduced four data fetching methods, with a focus on server-side fetching using fetch. Next.js extends the native fetch, adding data caching and revalidation logic. To improve performance, you should use caching whenever possible but set reasonable revalidation logic to ensure data freshness. Next.js recommends individually configuring each request's caching behavior for more granular control.
After introducing the four data fetching methods, Next.js provides recommendations and best practices for data fetching. Thanks to the powerful caching capabilities, you can fetch data where needed without worrying about performance issues from repeated requests.
References
- Data Fetching: Fetching, Caching, and Revalidating | Next.js
- Data Fetching: Data Fetching Patterns | Next.js