iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🦍

Achieving Client-side Only Rendering with dynamic export

に公開

Introduction

In Next.js rendering, regardless of whether you are using the App Router or Pages Router, SSR is the default behavior. Therefore, even if you specify the 'use client' pragma in the App Router, it may still be server-side rendered.
However, there are often cases where you want to render a specific component only on the client side.

In this article, I will explain how to render components only on the client side using the Next.js dynamic function, partly as a memo for myself.

The Flickering Issue

You might occasionally encounter this problem during implementation. In SSR (Server-Side Rendering), since the flow involves the server generating HTML and sending it to the client, the component may be displayed with its initial value for a split second before being fully applied during this process. This can cause flickering for the user.
I recently encountered this issue myself when using a theme management library like next-themes.

Using the dynamic Function

While you can resolve the flickering issue by using useEffect and useState to detect when the component has mounted, using the Next.js dynamic function allows you to configure specific components to be rendered only on the client side. Below, I will show an example and explain the details.

Overall Sample Code

Below is the overall picture of the code for this instance.

"use client";

import dynamic from "next/dynamic";
import { useTheme } from "next-themes";
import { Moon, Sun } from "lucide-react";
import { Button } from "./ui/button";

const ToggleMode = () => {
  const { theme, setTheme } = useTheme();
  const dark = theme === "dark";

  return (
    <Button
      variant="outline"
      size="icon"
      onClick={() => setTheme(dark ? "light" : "dark")}
    >
      {dark ? (
        <Sun className="hover:cursor-pointer hover:text-primary" />
      ) : (
        <Moon className="hover:cursor-pointer hover:text-primary" />
      )}
    </Button>
  );
};

export default dynamic(() => Promise.resolve(ToggleMode), { ssr: false });

1. Importing Necessary Modules

"use client";

import dynamic from "next/dynamic";
import { useTheme } from "next-themes";
import { Moon, Sun } from "lucide-react";
import { Button } from "./ui/button";
  • Specify the 'use client' pragma
  • Import dynamic
  • Please replace the other imports with ones appropriate for your specific case.

2. Defining the ToggleMode Component

Here, we define the ToggleMode component as an example, which displays an icon based on the current theme and allows switching themes when clicked.

const ToggleMode = () => {
  const { theme, setTheme } = useTheme();
  const isDark = theme === "dark";

  return (
    <Button
      variant="outline"
      size="icon"
      onClick={() => setTheme(isDark ? "light" : "dark")}
    >
      {isDark ? (
        <Sun className="hover:cursor-pointer hover:text-primary" />
      ) : (
        <Moon className="hover:cursor-pointer hover:text-primary" />
      )}
    </Button>
  );
};

The main reason flickering occurs in this implementation is that the initial state rendered on the server side is displayed for a moment before the next-themes library switches the theme on the client side. This issue happens due to the following factors:

Server-Side Rendering (SSR)

As mentioned at the beginning, Next.js uses server-side rendering by default. Therefore, during the initial render, the HTML generated by the server is sent to the client. At this point, since the theme settings haven't been applied by the client-side JavaScript, the default theme is displayed.

Delay in Client-Side Theme Application

There is a slight delay between when the useTheme hook is initialized on the client side and when the theme is applied. During this delay, the default light theme or the server-side theme is displayed, and then the theme is switched by the client-side JavaScript. This visible switch causes the flickering.

3. Exporting Using the dynamic Function

Finally, use the dynamic function to export the component asynchronously.
By setting the ssr: false option, you can disable server-side rendering and ensure it is rendered only on the client side.

export default dynamic(() => Promise.resolve(ToggleMode), { ssr: false });

Side Note

You can also perform dynamic imports within the importing component, but I used the export method because writing dynamic imports repeatedly on the importing side is tedious.

Also, by providing a component for the loading state, you can pass it as an argument just like a standard dynamic import. This allows you to use each component without needing to consider whether dynamic handling is required on the importing side.

export default dynamic(() => Promise.resolve(ToggleMode), { ssr: false, loading: () => <Loading />, });

Summary

As a result, the ToggleMode component is now rendered only on the client side, preventing flickering caused by server-side rendering.
Using the dynamic function is a technique I want to continue using as it leverages Next.js's flexible rendering options to improve user experience.

References

Lazy Loading

Discussion