How to unmount next/script on page change in next.js?

Viewed 1543

I want to load a script only on certain pages. Next.js recommends using next/script tag for it. However, when I navigate to some different pages I can still see the script present at the end of body in HTML.

enter image description here

import Script from "next/script";

const Comments = () => {
  return (
    <div className="giscus mt-16">
      <Script
        src="https://giscus.app/client.js"
        data-repo="GorvGoyl/Personal-Site-Gourav.io"
        data-repo-id="MDEwOlJlcG9zaXRvcnkyOTAyNjQ4MTU="
        data-category="Announcements"
        data-category-id="DIC_kwDOEU0W784CAvcn"
        data-mapping="pathname"
        data-reactions-enabled="0"
        data-emit-metadata="0"
        data-theme="light"
        data-lang="en"
        crossOrigin="anonymous"
        strategy="lazyOnload"
        onError={(e) => {
          console.error("giscus script failed to load", e);
        }}
      ></Script>
    </div>
  );
};

I suspect Next.js is not cleaning up the script on route change action. How do I make sure that scripts get removed on page change?

2 Answers

I was facing the same problem. Only solution that works for me is to reload the page as the routeChangeStart. I also made it in a custom Hook so can be reused with ease.

Custom Hook

import { useRouter } from "next/router";
import { useEffect } from "react";

export default function useScript(url) {
    const router = useRouter();
    useEffect(() => {
        const script = document.createElement("script");
        script.src = url;
        script.async = true;
        document.body.appendChild(script);

        // Needed for cleaning residue left by the external script that can only be removed by reloading the page
        const onRouterChange = () => {
            router.reload();
        };
        router.events.on("routeChangeStart", onRouterChange);

        return () => {
            router.events.off("routeChangeStart", onRouterChange);

            document.body.removeChild(script);
        };
    }, [router]);
}

Usage:

import useScript from "../utils/useScript"

export default function index(){
    useScript("<script-ulr>");
}

You can give unique url for every render. For example

<Script key={i} className='script-tag-js' src={`/path/to/script?v=${Math.random() * 999}`} type='module' />`

You can use this hook also.

import React, { ReactNode, useState, useEffect } from "react"
import { useRouter } from "next/router"
import Script from "next/script"

const useScript = (paths: string[]) => {
 const [scriptTag, setScriptTag] = useState<ReactNode>(<></>)
    const router = useRouter()


    useEffect(() => {
        if (!router.isReady) return

        router.events.on("routeChangeStart", () => {
            setScriptTag(<></>)
        })

        setScriptTag(<>
            {paths.map((x, i) => {
                return <Script key={i} className='script-tag-js' src={`${x}?v=${Math.random() * 999}`} type='module' />
            })}
        </>)


        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [router.asPath, router.isReady])

    return { scriptTag }

}

export default useScript

const Container: FC<ContainerProps> = ({ children, headerType, scripts = [] }) => {

    const { scriptTag } = useScript(['/assets/scripts/helpers/header.js', '/assets/scripts/helpers/modal.js', ...scripts])

    return <>
        <Header type={headerType} />
        {children}
        {scriptTag}
        <Footer />
    </>
}

export default Container
Related