Reuse Script Tag in Next.js without page refresh

Akifcan Kara
2 min readSep 28, 2022

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.

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.

Thank you so much for read this article. See you byee.

--

--