NextJS - Load external script on page component load - javascript

I've migrated an old website to Nextjs and i need the scripts to load every time a page component is loaded. But if i use the next/link component to navigate between pages, the scripts run only the first time the page is loaded. I've tried putting the inside the Head component in every page, but it still doesn't work. There is a workaround for this?

The two solutions to loading external scripts in Next.js seem to be to:
Load them using a combination of _document.js and next.config.js
_document.js
<script
type="text/javascript"
dangerouslySetInnerHTML={{ __html: process.env.jquery }}
></script>
next.config.js
const fs = require("fs")
module.exports = ({
env: {
jquery: fs.readFileSync("./public/js/jquery.js").toString(),
}
})
Reference: https://stackoverflow.com/a/65349130/2167762
Load them using React's useEffect hook, like with a custom useScript hook
/pages/index.js
import { useEffect } from "react"
const useScript = (url) => {
useEffect(() => {
const script = document.createElement("script")
script.src = url
script.async = true
document.body.appendChild(script)
return () => {
document.body.removeChild(script)
}
}, [url])
}
//export default function Home({ posts }) {
export default function Home() {
useScript("js/jquery.js")
return <p>Component</p>
}
Reference: https://stackoverflow.com/a/34425083/2167762
So which should you use?
The first seems to work "great" but is actually quite buggy -- I only get the JavaScript working sometimes after refreshing in development, and never in production.
(This would correspond to your problem with the scripts not loading when navigating with <Link>.)
The latter seems to work much better, although sometimes when I refresh in dev the scripts seem to load out of order, potentially causing runtime errors.
In production, however, the scripts seem to load & run consistently. I haven't explicitly tested <Link>, but it should work with the useEffect() method.

It'll be helpful to see the scripts you're loading. I faced a similar issue whiles using styled components in the version 9 of Nextjs. It had something to do with the server side rendering. Usually, using the Head should work. ie.
import Head from "next/head";
const HeadComponent = () => {
return (
<>
<Head>
<script src="https://code.jquery.com/jquery-3.6.0.js"></script>
</Head>
</>
);
};
You could also choose to dynamically import the scripts to the root of your app using:
import dynamic from "next/dynamic";
function MyApp({ Component, pageProps }) {
const Script = dynamic(() => import("../Script"));
return (
<>
<Component {...pageProps} />
<Script />
</>
);
}
export default MyApp;
I realised that the above makes all resources load in Nextjs

Related

How to add custom scripts bundle in NextJS

I have some legacy custom javascripts that I need to bundle and put them in _document.js as a link. The filename should include a hash.
What would be the best way to accomplish this?
I tried webpack configs regarding entry/output but they break NextJs build.
The problem is that we use things like window, document, etc that do crash in server side.
Ideally what is needed is to inject this into a tag, as compiled / babelified javascript code.
What I tried is
Webpack HTML Plugin plus other plugins like InlineChunk or
InlineSource plugins. They didn't work because they generate code in
an index.html that is not used by NextJS.
Using Raw Loader to get the file content. Doesn't work because it is
not babelified.
Adding a custom entry to the Webpack config, like scripts:
'path/to/my-entry.js'. Didn't work because it adds a hash name to the
file and I have no way of knowing it.
Adding a custom entry into the NextJs polyfills. I thought it made
sense, but the polyfill tag has a nomodule which prevents its code to
run on new browsers.
Another options is to add the javascript code as a string, and then using __dangerouslySetInnerHtml but the problem is that I lose linter and babel abilities there.
I tried adding it as a page, but crashes for local development and even on build
webpack.config.js
module.exports = (nextConfig = {}) =>
Object.assign({}, nextConfig, {
webpack(config, options) {
const nextJsEntries = config.entry;
config.entry = async () => {
const entries = await nextJsEntries();
entries['pages/rscripts'] = 'test/test.js';
return entries;
};
...
Then in _document.js
<script src={`${publicRuntimeConfig.ASSET_PREFIX}/_next/${this.props.buildManifest.pages['/rscripts'][2]}`} />
You can just import js file like import 'path/to/js_file' in your _app.js/app.tsx file
import "../styles/globals.css"
import "../js/test"
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
export default MyApp
This one works fine for me
I wanted to add another answer here as I came across this and I believe some things have changed in Next JS. Next now has this script component that you can use to load external scripts or dangerously set a script.
The Next.js Script component, next/script, is an extension of the HTML
element. It enables developers to set the loading priority of
third-party scripts anywhere in their application, outside next/head,
saving developer time while improving loading performance.
The cool thing is you can put them into whatever pages you want, maybe you have a script you want on a homepage, but not other pages, and Next will extract them and place them on the page based on the strategy you select. There are a few gotchas, can't load in the head, beforeInteractive is a little finicky, so I would read the link above and the actual API reference before making any choices.
import { useEffect } from 'react';
import Script from 'next/script';
function thirdPartyScript() {
useEffect(() => {
// just for fun. This actually fires
// before the onLoad callback
}, []);
return (
<Script
id="test-script"
strategy="afterInteractive"
src="/public/pages/scripts/test.js"
onLoad={() => {
console.log('Onload fires as you would expect');
}}
/>
);
}

NextJs router seems very slow compare to React Router

I have a website built in React Js and the same one on Next Js as well.
The problem which I am facing right now is, the router seems very slow in the nextJs compare to react-router-dom, It's taking almost 2-3 seconds to change the route.
Here are the URLs where you can feel the difference between the performance by moving around different pages.
https://cutt.ly/mhbPkOE (React Router Dom) vs
https://cutt.ly/BhbPvHv (NextJs)
I had read some comments on Github where few experts are saying that It will resolve in production. but It looks same in production too.
Please have a look at the following code
_app.jsx
// import App from 'next/app'
import React from "react"
import Router from 'next/router';
import "../static/sass/application.scss";
import "bootstrap/dist/css/bootstrap.min.css";
import "slick-carousel/slick/slick.css";
import "slick-carousel/slick/slick-theme.css";
import 'semantic-ui-css/semantic.min.css'
import { wrapper } from "../../redux/utils/store"
import App from 'next/app';
// A simple component that we created
import {LoaderOverlay} from '../components/Reusable'
class MyApp extends App {
constructor(props){
super(props)
this.state = {
isLoading: false,
}
Router.onRouteChangeStart = (url) => {
// Some page has started loading
this.setState({
isLoading: true,
}) // set state to pass to loader prop
};
Router.onRouteChangeComplete = (url) => {
// Some page has finished loading
this.setState({
isLoading: false,
}) // set state to pass to loader prop
};
Router.onRouteChangeError = (err, url) => {
this.setState({isLoading: false,})
};
};
render() {
const {Component, pageProps} = this.props
return (
<div>
{this.state.isLoading ? (
<LoaderOverlay/>
) : (
<Component {...pageProps} />
)}
</div>
)
}
}
export default wrapper.withRedux(MyApp);
_document.jsx
import Document, { Html, Head, Main, NextScript } from 'next/document'
class MyDocument extends Document {
static async getInitialProps(ctx) {
const originalRenderPage = ctx.renderPage
ctx.renderPage = () =>
originalRenderPage({
// useful for wrapping the whole react tree
enhanceApp: (App) => App,
// useful for wrapping in a per-page basis
enhanceComponent: (Component) => Component,
})
// Run the parent `getInitialProps`, it now includes the custom `renderPage`
const initialProps = await Document.getInitialProps(ctx)
return initialProps
}
render() {
return (
<Html lang="en">
<Head>
<link async rel="stylesheet" href="//cdn.jsdelivr.net/npm/semantic-ui#2.4.1/dist/semantic.min.css"/>
</Head>
<body>
<div className={'main-wrapper'}>
<Main />
</div>
<NextScript />
</body>
</Html>
)
}
}
export default MyDocument
Development mode (next dev) is much slower because the routes aren't pre-built.
All delay related to routing assuming you don't have any server side blocking data requirements via getInitialProps, getServerSideProps, should not be present when running production mode with next build followed by next start.
Not sure if you have found a fix for this yet, but I came across this article about "shallow routing". I can't see much improvement in my application when using it, but maybe it will help someone else:
https://nextjs.org/docs/routing/shallow-routing
Hey I think you are in your production mode.
That's why it is slow. But if you will host your site it will be pretty much like react only.
But then also if you want to routing fast
Then npm i next#4.2.3 --save will work fine..
to solve this issue followed the commands:
yarn build/nmp build
yarn start/npm start
I hope this will solve this issue

ReactJS discarding all included css files

I have a ReactJS application which works fine but except for one route, where the entire loaded css goes gray in the source code like this and is not applied to any element on the page:
My component is fairly simple and does one thing:
import React, { useEffect } from 'react';
import { OAuthHandlerService } from '<removed>';
import {LoadingAnimation} from "components/common";
interface IOAuthPageProps {
urlQuery: any;
}
const OAuthPage = (props: IOAuthPageProps) => {
useEffect(() => {
//OAuthHandlerService.onAuthTokenReceived(props.urlQuery);
}, []);
return (
<LoadingAnimation show={true} showSpinner={true} message='Redirecting...' />
);
}
export default OAuthPage;
I'm unable to see why all the loaded css is discarded, any ideas?
Fixed it. The issue was the route, it was going to non-existent route to load the css files:
this was fixed by specify the base URL:
<base href="/" />
The second issue was the mime type not specified, it works fine in other pages but I still specified type="text/css" and it is gone now.

ReactGA.initialize must be called first

I am getting the following warning multiple times (for multiple pages) despite me initializing at the root of my app. This makes me wonder if google analytics is even working properly?
[react-ga] ReactGA.initialize must be called first or GoogleAnalytics should be loaded manually
I am using ReactGA to handle my google analytics tags, and I cannot get it to work. According to the documentation and a handful of other questions about this online, all I need to do is insert this at my application root:
App.js:
import ReactGA from 'react-ga';
ReactGA.initialize('G-xxxxxxxxxx');
const app = () => (
// Root level components here, like routing and navigation
)
I am using Server Side Rendering, so I am making sure the window object exists before tracking. This line is put at the end of my imports on each of my pages:
example page.js:
import ReactGA from 'react-ga';
if (typeof(window) !== 'undefined') {
ReactGA.pageview(window.location.pathname + window.location.search);
}
function page() {
return(<div className="page">Hello, World</div>)
}
export default page;
At this point, there isn't a lot of information on how to set up Google Analytics for SSR applications, so I'm not entirely sure what I need to do to get this working. Any help is appreciated!
I finally found the solution after a lot of tinkering with potential solutions. Since pageview was being fired before initialize could finish, I tired delaying pageview as much as I could by placing it in componentDidMount(). That implementation looks like this:
App.js:
//imports
import ReactGA from 'react-ga';
ReactGA.initialize('UA-xxxxxxxxx-x');
const App = () => (
<div className="App">
<Navigation />
<AppRouting />
</div>
);
Page.js:
import ReactGA from 'react-ga';
class MyPage extends React.Component {
componentDidMount() {
ReactGA.pageview(window.location.pathname + window.location.search);
}
render() {
return(
<Component />
);
}
}
In functional components the same can be done using the useEffect hook
useEffect(() => {
ReactGA.pageview(window.location.pathname + window.location.search);
}, ['your dep'])

How to use React.lazy for a component of a module?

I am using the npm package called '#toast-ui/react-editor'. It includes a 'Viewer' react component. I could just use:
const Viewer = require("#toast-ui/react-editor").Viewer
But it increases the bundle size a lot. So I wanted to load it lazily whenever it is needed by using React.lazy. I am going to use it inside component:
<Viewer {...props} />
But I don't have any clue how to do it.
I tried this way, but didn't work.
const Lazy = React.lazy(() => import(require("#toast-ui/react-editor"))).Viewer;
I really want to know how to do it.
As Viewer is not a default component, it's not as simple as removing require, (which is not even needed, though).
You need to import it dynamically and then return the module as a default one (as that's what lazy expects and works with only).
const Viewer = lazy(() =>
import("#toast-ui/react-editor").then(module => {
return { default: module.Viewer };
})
);

Categories

Resources