Reuse Script Tag in Next.js without page refresh
Hello 👐,
In this article I want to talk about <Script /> tag in Next.js.
Problem:
We decided to migrate our vanilla javascript app to react.js/next.js application. But we didn’t convert all components to react.js style. We just copied our js files to new project and imported with <Script /> tag. At first everything looks fine but when I wraped some elements with <Link /> tag from next.js/link package an error occured.
The problem is <Script /> tag only run once. For example when I navigate to new page with <Link /> tag, <Script /> tag will execute but If I leave this page and come back again <Script /> tag won’t work this time. So I have to refresh page for execute <Script /> tag again.
And I made some research. Found this discussions.
- https://github.com/vercel/next.js/discussions/17919
- https://stackoverflow.com/questions/70758485/how-to-unmount-next-script-on-page-change-in-next-js/73728513#73728513
So, for execute script tag for every render just give a unique url.
<Script src={`/path/to/script?v=${Math.random() * 999}`} type='module' />`
I just gave unique version number so Next.js don’t use cached src.
And created a hook for maintain <Script /> tags.
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
- useScript tag takes paths parameter that represents script sources.
- After I created react fragment with useState hook. So I can rerender the <Scripts />
- I added two dependency for useEffect.
router.asPath
so when the url is changed this useEffect hook will fire again. And when this hook fires I put script tags inside the scriptTag variable. Also I clean the other script tags from previous page. Because we don’t use these scripts in new page. - And last but not least. I returned scriptTag (which is ReactNode type) variable.
Use this useScript hook:
I have a <Container /> component.
I wrap each component with container under the page folder.
const Container: FC<ContainerProps> = ({ children, headerType, scripts = [] }) => {
const { scriptTag } = useScript(['/assets/scripts/helpers/header.js', '/assets/scripts/helpers/modal.js', ...scripts])
return <>
<Header />
{children}
{scriptTag}
<Footer />
</>
}
export default Container
- This container accepts script but initially it is empty array.
- And I passed this scripts variable to useScript hook.
- I have some fixed scripts for every page I put these as a parameter. And spreaded scripts array. Because we shouldn’t have nested arrays.
- scriptTag is ReactNode that contains <Script /> tags. And I returned this scriptTag variable.
- So now after every route change new scripts will atached and previous scripts will remove.