ILCAI
ILCAI

Reputation: 1234

How to include dom-manipulating scripts into SSR Next.js App

I am experiencing following error:

Warning: Text content did not match. Server: "But I want to be altered by the client" Client: "Test"
    in div (at pages/index.tsx:17)
    in div (at pages/index.tsx:6)
    in HomePage (at _app.tsx:5)
    in MyApp
    in Container (created by AppContainer)
    in AppContainer

... with the following setup:

A NextJS App Component:

function HomePage() {
  return (
    <>
      <div id="test-div">I am rendered on the server.</div>
      <script src="http://localhost:8080/bundle.js"></script>
    </>
  );
}

export default HomePage;

(Note: The URL http://localhost:8080/bundle.js assumes webpack-dev-server is running and serving that resource)

The included "example" script:

const element = document.getElementById('test-div') as any;
element.innerHTML = 'But I want to be altered by the client';

In a simple setup I would just have a static html file, declaring a div element and including the "example" script.

But I would like to use NextJS, because I want to render dynamic (SSR) content into the page (eg. Text contents from a cms).

I noticed, that sometimes (if script execution takes some more ms of time), there is no error. Just do something time consuming in the example script.

Another hacky approach is to use setTimeout in the example script. I don't want to do that until I know why this is happening:

setTimeout(function() {
  const element = document.getElementById('test-div') as any;
  element.innerHTML = 'But I want to be altered by the client';
}, 20);

Upvotes: 7

Views: 17954

Answers (1)

Nikolai Kiselev
Nikolai Kiselev

Reputation: 6603

Next.js 11.0.0 and above

You can use Next.js Script component to load third-party scripts.

// pages/index.js
import Script from 'next/script'

function Home() {
  return (
    <>
      <Script src="https://www.google-analytics.com/analytics.js" />
    </>
  )
}

With next/script, you can define the strategy property and Next.js will optimize loading for the script.

Before Next.js 11.0.0

Browser and document, window objects are not available during server-side rendering.

You can initialize scripts that manipulate DOM after React component did mount.

useEffect(() => init(), [])

To add an external script you can do the following:

useEffect(() => require('../babylon.js'), [])

To include a script from another server you can add a script tag:

useEffect(() => {
  const script = document.createElement("script");
  script.src = "http://localhost:8080/bundle.js";
  script.async = true;
  document.body.appendChild(script);
},[])

If you're adding DOM listeners you would also need to do cleanup.

Using the Effect Hook

Effects with Cleanup

Upvotes: 19

Related Questions