Lazy Load, Dynamic Import, and Preload in Next.js
Chapter 11 focuses on enhancing Next.js applications by implementing lazy loading, dynamic importing, and preloading techniques. It discusses how these strategies contribute to efficient data handling and improve user experience.
In this chapter, you will learn
Chapter 5 introduced lazy loading in the client-side context, demonstrating how separating non-essential content into different bundles can enhance initial rendering and user experience. While Next.js accelerates initial rendering with static rendering, streaming, and server-side rendering, implementing lazy loading and preloading further optimizes the application. These techniques are especially beneficial for users who don't require access to all content immediately.
Dynamic Load in Next.js
In Next.js, next/dynamic
is a tool for dynamic component importing, it is a composite of React.lazy()
and Suspense
. It's particularly useful for optimizing the loading of components that are not immediately necessary on the initial render, thereby enhancing the performance and user experience of your application.
In the example below, the Gallery
component is imported dynamically using next/dynamic
. This means that Gallery
will not be part of the main JavaScript bundle that loads when the page initially loads. Instead, it will be loaded only when the App
component renders it:
In this case, the Gallery
component is fetched and loaded asynchronously, only when the App
component needs it. This approach reduces the initial load time of your application because the browser downloads fewer resources upfront.
Furthermore, next/dynamic
allows for specifying a fallback component that is displayed while the dynamic component is being loaded:
This technique is useful for client components, allowing them to be bundled separately and loaded as needed.
Implementing the UserDetailCard
Consider a UserDetailCard
component that might not be immediately needed by every user. We can apply lazy loading to enhance performance:
Here, the UserDetailCard
is dynamically loaded only when required, reducing the initial load.
And in the UserDetailCard
we could do the skeleton just like above:
And when we click a Friend
you can see there is an additional request send (for a JavaScript chunk), and then following a network request.
You might be wondering if we could do the same preload
technique as we have seen in Chapter 6, and the answer is yes. We could use the same useSWR
package in Next.js, but let's try alternative - define a cache and see how we can use Server-Side Rendering to hide the request details.
Preload in Next.js
Preloading can be used to fetch data in advance, such as when a user hovers over a button:
While preload
is defined as:
The preload
function fetches the user's details when the user hovers over the Friend
component. This is accomplished by triggering the preload
function on the onMouseEnter
event of the button element. When the mouse pointer enters the button's area, getUserDetail(id)
is called, and it fetches the details of the user associated with the Friend
component.
Note here in line 8 above, the void
operator evaluates the expression (getUserDetail(id)
) and then returns undefined, which is handy if we want to execute a function but don't care of the return value.
To optimize the preloading process and prevent unnecessary repeated data fetches, we implement an internal caching mechanism using a Set. Here's the refined explanation for the provided code snippet:
In the apis.ts
file, we introduce a caching strategy using a Set
to manage the preloading of user details efficiently. The Set
, named preloadedUserIds
, stores user IDs for which details have been preloaded. This unique collection ensures that each user's details are fetched only once, avoiding redundant network requests.
The cache
function in React, currently in the Canary (experimental) version, is designed for use with React Server Components. It allows you to create a cached version of a function, which can be particularly useful for data fetching or expensive computations. When you call the cached function with specific arguments, it first checks if there's a cached result. If so, it returns this result; if not, it executes the function, stores the result in the cache, and then returns it.
This approach can significantly optimize performance by avoiding repeated executions of the same function with the same arguments. However, it's important to note that each call to cache
creates a new function, so calling it multiple times with the same function will not share the cache. Additionally, cache
only works in Server Components and is for experimental use as of now.
The getUserDetail
function fetches user details and is enhanced with React's cache
function. This caching mechanism ensures the asynchronous operation's results are stored, reducing unnecessary data fetching. When the preload
function is called with a user ID, it first checks if the ID is already in preloadedUserIds
. If not present, it fetches and caches the user's details. This approach optimizes the preloading process, ensuring it only occurs when necessary and thereby enhancing application performance.
The effective use of caching in the preload
function demonstrates a keen understanding of optimizing network interactions, ensuring that data fetching is both efficient and necessary. This consideration for performance leads us to another crucial aspect of building web applications in Next.js: the client component strategy.
Client Component Strategy
Pushing client components to the leaf of the component tree, particularly in a server-side rendering environment like Next.js, is a strategic approach for optimizing web application performance. By doing so, you ensure that most of the page is server-rendered, which speeds up the initial load time. Interactive elements that require client-side JavaScript are loaded only where necessary, making the page interactive more quickly without overburdening the initial load.
In the provided code snippets, we have two versions of the Friends
component. The first version marks the entire component as a client component, while the second version pushes the client-side logic down to the individual Friend
component.
First Version - Entire Component as Client-Side
The first Friends
component is wrapped with use client
, making the entire component and all its children client-side only. This means that even simple, non-interactive parts of this component won't render until the client-side JavaScript loads and executes, which can delay the initial rendering and interactivity of the page.
Second Version - Client Logic at Leaf Node
The second approach optimizes performance by removing the use client
directive from the Friends
component. Instead, it delegates client-specific logic to the Friend
component. This way, the Friends
component itself can be pre-rendered on the server, and only the interactive parts within each Friend
component are handled on the client-side. This granular approach speeds up the initial page load, as less JavaScript needs to be downloaded and executed to render the initial view.
And we then move NextUIProvider
usage into friend
component.
By applying the client component at the leaf node (Friend
), you enhance the user experience with faster initial load times and progressively enhance the page with interactive elements as needed. This approach aligns well with modern web development best practices, particularly in frameworks like Next.js, where balancing server-side and client-side rendering is key to achieving optimal performance and user experience.
Fantastic work – you've achieved it!
You have made remarkable progress, and I hope you feel proud of the skills you have acquired. Your dedication and hard work have paid off, and you are now equipped with valuable knowledge for your web development journey.
As you continue to grow and apply what you have learned, I would like to extend an invitation to deepen your understanding even further. I have written a book that complements the topics covered in this course, offering more insights and advanced techniques. This book is designed to be a valuable resource for you, providing detailed explanations and practical examples that build upon the foundations we have established here.
You can find the book in the section following this course. Whether you are looking to refine your skills or tackle more complex projects, this book can be your guide to the next level of your web development journey.
Thank you for choosing this course, and I look forward to assisting you in your continued learning!
Advanced Data Fetching Patterns in React
This book is your essential guide to mastering the art of efficient data fetching in React. Discover innovative strategies and the latest features to elevate your React applications, transforming them into models of performance and efficiency. Dive in and reshape the way you handle data in your React projects!