react-player icon indicating copy to clipboard operation
react-player copied to clipboard

Next.JS / React 18 - Hydration Error

Open dmrobbins03 opened this issue 3 years ago • 21 comments

Current Behavior

Unless react-player is loaded on the client-side using dynamic/no-ssr or useEffect, React will panic with a hydration error.

Expected Behavior

Use of SSR should not result in a hydration error.

Steps to Reproduce

  1. Using Next.JS, import react-player and follow typical implementation instructions.
  2. Error

Other Information

There is a great thread on Stack Overflow here (https://stackoverflow.com/questions/71706064/react-18-hydration-failed-because-the-initial-ui-does-not-match-what-was-render) where devs list various solutions to related issues.

Examples:

  • react and react-dom may need to be updated.
  • Wrapping tags improperly, such as <div> inside <p> will cause the error.
  • Lazy modules need to be wrapped in <Suspense>.

Please check again these scenarios in the codebase. Thank you!

dmrobbins03 avatar Jul 08 '22 18:07 dmrobbins03

I have the same issue and I get this error while using react-player: Error: Hydration failed because the initial UI does not match what was rendered on the server.

even when I set a preview image for light mode and no change happens in UI during hydration, I get the hydration error. if you find any solution please let me know

minamobahi avatar Jul 09 '22 09:07 minamobahi

Have you tried this?

It's basically checking the window.

const Component: React.FC<SomeProps> = ({url}) =>{
  const [hasWindow, setHasWindow] = useState(false);
  useEffect(() => {
    if (typeof window !== "undefined") {
      setHasWindow(true);
    }
  }, []);
  return (
    <>
      {hasWindow && <ReactPlayer url={url} />}
    </>
  )

narasaka avatar Jul 09 '22 10:07 narasaka

For temporary solution you can downgrade react to 17.0.2 for example

ChobotkoD avatar Jul 09 '22 10:07 ChobotkoD

Have you tried this?

It's basically checking the window.

const Component: React.FC<SomeProps> = ({url}) =>{
  const [hasWindow, setHasWindow] = useState(false);
  useEffect(() => {
    if (typeof window !== "undefined") {
      setHasWindow(true);
    }
  }, []);
  return (
    <>
      {hasWindow && <ReactPlayer url={url} />}
    </>
  )

I've tried this and of course it works! but this means rendering the react-player component on the client side so I need to render another thing ( like an image) on the server side. I wish the react-player would handle the SSR by itself

minamobahi avatar Jul 09 '22 12:07 minamobahi

I just found out that in ReactPlayer.js the window is used in the component's rendering which is not recommended in this document and leads to Hydration Error

minamobahi avatar Jul 09 '22 13:07 minamobahi

I agree that ReactPlayer should be SSR-friendly out of the box, @cookpete what do you think?


For now, until that happens, another workaround is to lazy-load the player using Next.js dynamic imports, as mentioned by @inderrr in this comment:

import dynamic from 'next/dynamic';

const ReactPlayer = dynamic(() => import('react-player/lazy'), { ssr: false });

karlhorky avatar Jul 14 '22 16:07 karlhorky

I agree that ReactPlayer should be SSR-friendly out of the box, @cookpete what do you think?

Sounds great. I just don’t have the time or knowledge to implement, test and document it.

cookpete avatar Jul 14 '22 18:07 cookpete

I agree that ReactPlayer should be SSR-friendly out of the box, @cookpete what do you think?

Sounds great. I just don’t have the time or knowledge to implement, test and document it.

Can you upgrade react and react-dom or does it break? I can also submit a PR if that helps.

dmrobbins03 avatar Jul 18 '22 04:07 dmrobbins03

The versions of the react and react-dom packages are unrelated here, SSR support is more about things like browser globals being undefined or rendering something different on client and server, which @minamobahi mentioned in the comment above:

the window is used in the component's rendering

If you want to submit a PR, then probably best would be to:

  1. Read a bit into the errors that can come from window being used in React components, such as the common ReferenceError: window is not defined error:

https://dev.to/vvo/how-to-solve-window-is-not-defined-errors-in-react-and-next-js-5f97

  1. Come up with a strategy for how react-player should behave when browser globals such as window are not available. It seems like there is some work being done around window detection already:

https://github.com/cookpete/react-player/blob/9775bb755af195e6f7e8bacfed436fcefb135354/src/ReactPlayer.js?rgh-link-date=2022-07-09T13%3A01%3A03Z#L12-L18

But react-player is not rendering the same thing on client and server (which is where the Next.js error comes from).

karlhorky avatar Jul 18 '22 07:07 karlhorky

Hi, I have the same issue and I get this error while using react-player: Error: Hydration failed because the initial UI does not match what was rendered on the server.

yogesh2503 avatar Jul 21 '22 05:07 yogesh2503

@yogesh2503 this works fine: https://github.com/cookpete/react-player/issues/1474#issuecomment-1184645105

MathiasGmeiner avatar Jul 25 '22 09:07 MathiasGmeiner

Thank Its work for me

yogesh2503 avatar Jul 25 '22 10:07 yogesh2503

Its work for me

To dynamically load a component on the client side, you can use the ssr option to disable server-rendering. This is useful if an external dependency or component relies on browser APIs like window.

import dynamic from 'next/dynamic'

const DynamicHeader = dynamic(() => import('../components/header'), { ssr: false, })

koliada-max avatar Dec 08 '22 21:12 koliada-max

Its work for me

To dynamically load a component on the client side, you can use the ssr option to disable server-rendering. This is useful if an external dependency or component relies on browser APIs like window.

import dynamic from 'next/dynamic'

const DynamicHeader = dynamic(() => import('../components/header'), { ssr: false, })

Thank you, just what I was looking for 👍

makyinmars avatar Dec 19 '22 23:12 makyinmars

The versions of the react and react-dom packages are unrelated here, SSR support is more about things like browser globals being undefined or rendering something different on client and server, which @minamobahi mentioned in the comment above:

the window is used in the component's rendering

If you want to submit a PR, then probably best would be to:

  1. Read a bit into the errors that can come from window being used in React components, such as the common ReferenceError: window is not defined error:

https://dev.to/vvo/how-to-solve-window-is-not-defined-errors-in-react-and-next-js-5f97

  1. Come up with a strategy for how react-player should behave when browser globals such as window are not available. It seems like there is some work being done around window detection already:

https://github.com/cookpete/react-player/blob/9775bb755af195e6f7e8bacfed436fcefb135354/src/ReactPlayer.js?rgh-link-date=2022-07-09T13%3A01%3A03Z#L12-L18

But react-player is not rendering the same thing on client and server (which is where the Next.js error comes from).

Ok you were definitely on to something with this and it took me some digging but I think I figured out why this is so incompatible with SSR.

There was one bit of magic that I noticed in the Readme that struck me as odd but I couldn't figure out why. For Youtube embeds, there is an example of the actual url of the YT video, not the embed url or the id of the video. In other implementations I've seen or done for YT embeds, typically there is some more configuration needed around stripping the embed id or specifying the poster image (for a "light" version) but this handled it.

In the line you references, we are returning null on the server side, since Suspense doesn't work server side pre- React 18. I think now we could probably set a flag or do a check for the React version to enable Suspense/lazy. But that just allows the code to attempt to run in SSR.

The next piece I've seen is the call to window in the file Preview.js, and this is where the magic with the youtube URL happens:

https://github.com/cookpete/react-player/blob/662082a2f2edc863e8ca9cefdd7a3c88ad49ea0d/src/Preview.js#L42

Basically, if the url is for youtube and a poster image isn't provided, we are fetching a YT endpoint that provides some meta about the video, including a poster image uri and html for an iframe.

I'm going to make an attempt to fix this in a universal way in a fork I made, but one big thought is on my mind: Should we be doing it this way?

I think there might be a solution that is right enough for all React environments, but ultimately if this component is handling a certain level of data fetching, it needs to have integrations with specific frameworks that can handle this in the way that it intends to do so. Since SSR and SSG is becoming the norm for react, my thoughts on a refactor would be some or all of the following:

  • Decouple optimizations like this throughout the repo that work to gather data not provided by the user
  • Provide those data fetching functions as helpers available to get the data in the way that the given framework suggests
  • Potentially provide examples including framework-specific implementations of the component
  • Provide a React 18 implementation that doesn't opt-out of Suspense in SSR

If I can find time, I will try to experiment with some of these concepts, but would be interested in feedback from @cookpete on these thoughts also.

panzacoder avatar Mar 11 '23 06:03 panzacoder

@panzacoder I appreciate the thought you’ve put into this. Unfortunately I think we’re at the point where a proper solution is simply not worth the time and effort it would take – you might as well rewrite the library from scratch. I have been gradually rewriting this with typescript, hooks, storybook, etc, but getting time to work on it is tricky.

There is no way to avoid using window as the whole point of the component is to load third party scripts to play a given url. There is nothing we can really render during SSR because we just don’t know what markup will be rendered by these scripts. I guess we could use noembed to fetch a thumbnail and render that, but I don’t know how intrusive that would be to the codebase. At this point I would much rather keep things simple considering how complex this library has become to satisfy such a wide range of developers/platforms.

Some ideas:

  • If the problem circles around lazy and Suspense should we just release v3 with it removed completely, and just let the developer decide which players to include and how to lazy load them? The majority of users are only ever using one player, and more and more users are using a platform that has lazy loading built in, like next/dynamic.
  • Could we somehow wrap react-player in a way that supports Next.js? So the changes in this library are minimal and we just push Next.js users towards react-player-next (or whatever we call it).

cookpete avatar Mar 12 '23 16:03 cookpete

I agree that ReactPlayer should be SSR-friendly out of the box, @cookpete what do you think?

For now, until that happens, another workaround is to lazy-load the player using Next.js dynamic imports, as mentioned by @inderrr in this comment:

import dynamic from 'next/dynamic';

const ReactPlayer = dynamic(() => import('react-player/lazy'), { ssr: false });

For anything not Next.... im presuming using loadable components would work a treat.

joebentaylor1995 avatar Mar 24 '23 23:03 joebentaylor1995

I agree that ReactPlayer should be SSR-friendly out of the box, @cookpete what do you think?

For now, until that happens, another workaround is to lazy-load the player using Next.js dynamic imports, as mentioned by @inderrr in this comment:

import dynamic from 'next/dynamic';

const ReactPlayer = dynamic(() => import('react-player/lazy'), { ssr: false });

works for me. thanks ! ;)

Vince66000 avatar Mar 26 '23 08:03 Vince66000

this comment

Having the same issue with next13 but this seems to be indeed the best solution right now. I've also took a look at the Next13 documentation: https://nextjs.org/docs/app/building-your-application/optimizing/lazy-loading

Joaoalves89405 avatar Jun 13 '23 17:06 Joaoalves89405

It is not possible to be used on SSR(what I think it is intended for), so we have to use it on the client side:

"use client";

import coreUtils from "@/core/application/utils";
import dynamic from "next/dynamic";

const ReactPlayer = dynamic(() => import("react-player/lazy"), { ssr: false });

Otherwise, we will have the next issue:

 ⨯ node_modules/.pnpm/[email protected][email protected]/node_modules/react-player/lazy/Player.js (32:112) @ _inherits
 ⨯ TypeError: Super expression must either be null or a function
    at __webpack_require__ (/home/luisalaguna/Projects/challenge-trt/.next/server/webpack-runtime.js:33:42)
    at __webpack_require__ (/home/luisalaguna/Projects/challenge-trt/.next/server/webpack-runtime.js:33:42)
    at Function.__webpack_require__ (/home/luisalaguna/Projects/challenge-trt/.next/server/webpack-runtime.js:33:42)
null

SalahAdDin avatar Oct 02 '23 11:10 SalahAdDin

Solution in the Next.js doc

'use client'

import { useState, useEffect } from 'react'
 
export default function App() {
  const [isClient, setIsClient] = useState(false)
 
  useEffect(() => {
    setIsClient(true)
  }, [])
 
  return {isClient ? <ReactPlayer /> : <p>The video player cannot render on the server side</p>}
}

KelvinQiu802 avatar Jan 18 '24 14:01 KelvinQiu802

Solution in the Next.js doc

'use client'

import { useState, useEffect } from 'react'
 
export default function App() {
  const [isClient, setIsClient] = useState(false)
 
  useEffect(() => {
    setIsClient(true)
  }, [])
 
  return {isClient ? <ReactPlayer /> : <p>The video player cannot render on the server side</p>}
}

from https://github.com/cookpete/react-player/issues/1428, if above doesn't work, try return {isClient ? <ReactPlayer /> : null}

christopher-theagen avatar Mar 04 '24 14:03 christopher-theagen