Next.js Hydration Window Issue

May 29, 2024 · Updated on May 30, 2024

Coming from a React environment, it was natural for me to write a page view tracker as below:

"use client";
import React from 'react';
 
export const TrackPageView = () => {
	React.useEffect(() => {
		trackView({
			url: window.location.href,
			referrer: document.referrer,
		});
	}, [window.location.href, document.referrer]);
 
	return <></>;
}

But this doesn’t work in Next.js. If you test it out, you would either get:

ReferenceError: window is not defined

which is clear enough, or you might encounter:

TypeError: Response body object should not be disturbed or locked

This happens because you’re calling trackView server action and the signal is aborted. It seems this issue arises due to Next.js hydration. Even though the component is set to be a client component, hydration renders it during the server pre-render.

What is Next.js Hydration?

Hydration in Next.js refers to the process where the server-rendered HTML is made interactive on the client side. Essentially, the HTML generated by Next.js during the server-side rendering (SSR) phase becomes a static page that is then "re-animated" or hydrated into a fully interactive app once the JavaScript loads and runs in the browser.

Benefits of Hydration

Hydration offers several advantages:

  1. SEO and Performance: By rendering the initial HTML on the server, Next.js ensures that search engines can index the content, improving SEO. Additionally, the initial load time is faster because the user receives a fully-rendered page almost immediately.
  2. Interactivity: Once the static HTML is hydrated, the page becomes interactive, allowing for a dynamic user experience with all the benefits of React.
  3. Consistency: Hydration ensures that the server-rendered HTML and the client-side React components are synchronized, providing a seamless user experience.

Issues with Hydration

While hydration is beneficial, it comes with its own set of challenges:

  1. Window is Not Defined: During server-side rendering, there is no window object, leading to errors if your code tries to access it. This is because window is a browser-specific global object that is unavailable in the Node.js environment where the server-side code runs.
  2. Hydration Mismatches: If the server-rendered HTML does not match the HTML generated by React on the client, hydration can fail, causing UI glitches or errors.
  3. Performance Overhead: Hydration can add a performance overhead because the browser needs to parse and execute the JavaScript code to make the page interactive.

The Fix

To address these hydration issues, you can make several adjustments. A fast solution you can adapt involves using Next.js hooks effectively:

"use client";
import React from "react";
import { usePathname } from "next/navigation";
 
export const TrackPageView = () => {
	const pathname = usePathname();
 
	React.useEffect(() => {
		trackView({
			url: pathname,
			referrer: document.referrer,
		});
	}, [pathname]);
 
	return <></>;
};

Also, by removing document.referrer from the dependencies array, I prevent unnecessary potential hydration issues. This ensures that useEffect only triggers when the pathname changes, avoiding the issues caused by the initial hydration process. Next.js ensures that everything inside useEffect will run on client-side only but it’s not the same case with hook dependancies so during hydration you might get an error saying that document is not defined.

Food for thought

Hydration is a powerful feature of Next.js that bridges the gap between server-rendered and client-rendered applications. While it brings many benefits, it also requires careful handling of client-side code.

In summary, when working with Next.js:

  1. Be cautious with direct window and document usage.
  2. Understand the hydration process and its implications.
  3. Use Next.js hooks and utilities to manage client-side interactions effectively.

Till next time!