Reputation: 63
When I use the <Link>
tag in NextJs to navigate between pages it doesn't rerun my scripts after changing pages. It only runs the scripts after the first page load or when I press reload. When using plain <a>
tags instead, it works fine because the page reloads after each navigation. As far as I can tell this happens because the <Link>
tag makes it a Single-Page Application and doesn't refresh the page when navigating between pages.
I would greatly appreciate anyway to have it rerun the scripts when navigating between pages or to have it refresh the page without using just plain <a>
tags and losing the Single-Page Application functionality.
This code doesn't refresh the page
<Link href="/page1">
<a>Page 1</a>
</Link>
<Link href="/page2">
<a>Page 2 </a>
</Link>
This code does refresh the page
<a href="/page1">Page 1</a>
<a href="/page2">Page 2 </a>
I load in all my scripts using a scripts component
export default const MyScripts = () => {
return (
<Script
strategy="afterInteractive"
type="module"
src="/scripts/myScript.js"
onReady={() => {
console.log("Ready")
}}
/>
)
}
One thing I've noticed is that the above onReady
function fires each time I change pages. Maybe someone knows how to run the myScript.js
from the onReady
.
Upvotes: 2
Views: 2563
Reputation: 10096
Caution: The hack of using
history.pushState
will not work reliably, especially when DOM elements need to be fetched usingdocument.querySelector*
. Anyway, it's also better to avoid hacks if you can.
The recommended way for Next.js is to use the Script
component with the inline script.
Simply move the Javascript code from your script
tag into a Script
component. Remove the HTML script
tag and any script file as you no longer need them.
import Script from 'next/script';
// Inside your exported component:
<Script id="myScript">
// Paste the contents of /scripts/myScript.js here (and remove that file)
</Script>
Another clean way to do this is to use the useEffect
React hook. In class-based components, you can use componentDidMount
.
This idea was proposed here: samplep182's comment in: Normal scripts "" not working after page navigation · Issue #4477 · vercel/next.js
Simply move the Javascript code from your script
tag into a useEffect
block. Remove the script
tag and any script file as you no longer need them.
import { useEffect } from 'react';
// Inside your exported component:
useEffect(() => {
// Paste the contents of /scripts/myScript.js here (and remove that file)
}, []);
Notes:
It's recommended to have an empty array []
as the dependency list, to execute this on page load only.
For external scripts, please use the Script
component with the src
attribute.
Upvotes: 0
Reputation: 131
Even though it's not explicitly mentioned in the next/script docs, the scripts added via the <Script>
component stay mounted on navigation. This is THE optimization that the component is supposed to do:
<head>
, so the initial app load is faster<Link >
). <script>
is added to the body
. The script is often third-party code adding properties to window
.body
and window
properties stay presentWhen you need to run imperative code each time the page is visited/mounted, I'd go for useEffect
. If you need to re-run some script as a whole on mount (not just re-use some of the work already done by it), you can:
script.src
should work (script.src += '?x=x'
))<script>
tag - it should get mounted/unmounted just like other elementsUpvotes: 0
Reputation: 63
I used to this to solve it. It rerun what you want to be rerun each time the URL changes.
function name(history){
var pushState = history.pushState;
history.pushState = function(state) {
// YOUR CUSTOM HOOK / FUNCTION
console.log('I am called from pushStateHook');
return pushState.apply(history, arguments);
};
})
name(history)
My implementation in myScript.js
function myFunctions(){
//my code
}
function urlChange(history){
let pushState = history.pushState;
history.pushState = function(state) {
myFunctions();//I insert my code here
return pushState.apply(history, arguments);
};
}
urlChange(history);
Also I'm pretty sure history.pushState = function(state){}
is what runs each time the url changes not the urlChange()
function.
Upvotes: 1
Reputation: 330
Could try to use the router
object from the useRouter
hook. Here's how you'd do that:
export const YourComponent = () => {
const router = useRouter();
...
return (<>
<button onClick={() => router.push({ pathname: '/page1' })}/>...</button>
// Or you can also use a div if you don't want it behaving like a button
<div onClick={() => router.push({ pathname: '/page1' })}/>...
...
If this doesn't work, then I'd check for what the code for the Scripts
component is doing and how it's reacting to state.
Let me know if that helps!
Upvotes: 0