Reputation: 49480
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.
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?
Upvotes: 15
Views: 11158
Reputation: 2514
This is an extension of @Chaitanya Kulkarni 's answer. It is different because it leverages next/script.
Benefits of using next/script are that ScriptWithCleanup component accepts the same props as next/script like onLoad and onReady. And, duplicate script tags aren't added to the body in case multiple components with the same script tag are rendered on the page.
import { useEffect } from 'react';
import { useRouter } from 'next/router';
import Script, { ScriptProps } from 'next/script';
export const ScriptWithCleanup = (props: ScriptProps) => {
const router = useRouter();
useEffect(() => {
// Needed for cleaning residue left by the external script
// that can only be removed by reloading the page
const onRouterChange = (newPath: string) => {
window.location.href = router.basePath + newPath;
};
router.events.on('routeChangeStart', onRouterChange);
return () => {
router.events.off('routeChangeStart', onRouterChange);
};
}, [router, props]);
return <Script {...props} />;
};
import { ScriptWithCleanup } from './ScriptWithCleanup';
export const YourComponent = () => {
return (
<div>
<ScriptWithCleanup
src="/js/theScript.js"
onLoad={() => console.log('Stripe script loaded')}
/>
</div>
);
};
Upvotes: 2
Reputation: 74
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.
import { useRouter } from "next/router";
import { useEffect } from "react";
export default function useScript(url: string) {
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 = (newPath: string) => {
window.location.href = router.basePath + newPath;
};
router.events.on("routeChangeStart", onRouterChange);
return () => {
router.events.off("routeChangeStart", onRouterChange);
document.body.removeChild(script);
};
}, [router, url]);
}
Just need to call useScript with url of javascript
import React from "react";
import NavBar from "../src/components/NavBar";
import useScript from "../src/hooks/useScript";
type Props = {};
const ScriptLoader = (props: Props) => {
useScript("/js/theScript.js");
return (
<>
<NavBar />
<div className="container">This is normal Page</div>
</>
);
};
export default ScriptLoader;
If you want to access property set on window object from the script its best to add setTimeout to check if that object is present or not as the loading of script might take some time, after that you can set some flag to true
Upvotes: 5
Reputation: 9
I did a lot of crawling on the internet and tried a lot of different methods from forums because this seems to be an issue with next.js but the one that worked for me with the least amount of code was adding a script tag with javascript in the useEffect hook. I tried it randomly and I didn't think it would work to unmount it on it's own but it did without any extras. I have the brackets empty so it only runs on mount.
export default function Page() {
useEffect(() => {
const contentDiv = document.getElementById("content");
const newScript = document.createElement("script");
newScript.src = "url";
newScript.id = "script-id";
newScript.async = true;
// add rest of your script items
contentDiv?.appendChild(newScript);
}, [])
return (
<div className='content'>
</div>
)
}
Upvotes: 0