how do you add external javascript in Next.js? - javascript

I'm learning Next.js and have spent hours trying to add external javascript.
I want to load this external script I named query.js into /pages/index.js
console.log("script loaded!")
function hello(){
console.log("hello!")
}
The script does load when I imported the script on _app.js
import '../styles/globals.css'
import '../public/static/query.js'
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
export default MyApp
but calling the function like this on index.js returned "ReferenceError: hello is not defined" even though the script already loaded.
export default function Home() {
return (
/* some div */
{hello()}
/* the rest of the code */
)
what did I do wrong? I want the script to do some data processing on the browser. I have tried

it totally depends on how you plan to import your JS file.
Your current approach
your current approach is creating a side effect only module which your import statement initializes- that is why you see the console.log statement. to make this work just attach hello function to the window object which is not recommended.
Recommended approach
Usually, it's best to create modules without side effects. in that case to make this work you should export your function and import it in whatever module that you need.
here's an example:
console.log("script loaded!")
export function hello(){
console.log("hello!")
}
and in your module
import {hello} from 'wherever';
export default function Home() {
return (
/* some div */
{hello()}
/* the rest of the code */
)

To add external javascript in Next.js you can use the Script component from next/script since v11.0.0.
Example from their website:
import Script from 'next/script'
export default function Home() {
return (
<>
<Script src="https://www.google-analytics.com/analytics.js" />
</>
)
}
More info in their official docs.

Related

Next.js throwing Error: Error: NextRouter was not mounted when using _document.js inside pages folder

I want to be able to customize the HTML based on the current locale. I have created a _document.js inside pages directory. And I used this code:
import { Html, Head, Main, NextScript } from 'next/document'
import { useRouter } from 'next/router'
export default function Document() {
const { locale } = useRouter();
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
But I get this error:
Error: Error: NextRouter was not mounted. https://nextjs.org/docs/messages/next-router-not-mounted
When I go to the URL specified, I see them saying that I'm using logic outside <Main />. Thus, how can I access locale inside _document.js?
I found NextRouter was not mounted Next.JS but even using next/navigation did not work for me, and I get this error:
Error: invariant expected app router to be mounted
useRouter() hook runs on the browser while _document.js runs on the server; that's the problem. For example, if you add a console.log('Hello Word') in Document before the return, it gets printed only in your development server, not on the browser.
You shouldn't be trying to use a logic related to hooks in _document.js, as it's not meant for, as the doc says:
React components outside of <Main /> will not be initialized by the browser. Do not add application logic here or custom CSS (like styled-jsx). If you need shared components in all your pages (like a menu or a toolbar), read Layouts instead.
It happened to me in the client component of app directory.
Instead of this
import { useRouter } from 'next/router'
import from
import { useRouter } from "next/navigation";

Is an explicit export necessary when a function is made available from an index file?

I use vanilla JavaScript to define various components in separate folders. For example, the definition for the accordion component can be found in accordion/accordion.js and is structured as follows:
import toggleCollapsible from "../../helpers/toggle-collapsible";
const SELECTOR_ACCORDION = ".accordion";
const SELECTOR_SLAT = ".accordion-slat";
function Accordion(accordion) {
...
}
Array.from(document.querySelectorAll(SELECTOR_ACCORDION)).forEach((accordion) => Accordion(accordion));
export default Accordion;
There is also an index.js file which is used only for making the component available elsewhere via export * from "./accordion";
Leaving out export default Accordion; part in accordion.js seems to work just fine. So is there any reason why I shouldn't drop it and simply use just export * from "./accordion"; in the index file?
If you don't export anything from the module, then export * from ... will also export nothing.
However, just importing that module has that side effect of activating the array for elements on the page, which feels unclean (and indeed, might not work if the script is in <head>, for instance...).
I'd wrap the Array.from() stuff in a function that's exported:
export function activateAccordions() {
Array.from(...);
}
and then import and call that in your index.
import {activateAccordions} from "./accordion";
activateAccordions(); // TODO: might need to call this only after the page is loaded
Then, if you additionally need to be able to accordion something arbitrary,
export function Accordion()
and import it too...

How do I access the object inside a script file within my own jsx file?

I am using React, and I am wondering how I can access the object that I have included as script in my html file within my own jsx file
This is an example that I got:
<script src="url-to-some-script"></script>
<div id="an-id-wrapper">
<div id="an-id"></div>
</div>
<script>
var settings = { config: "some-config", id: "an-id" };
TheObjectThatINeedToAccessFromScript.initialize(settings);
</script>
I want to do something like:
Add the script in my html file
Place the div in some React component
Be able to reach the TheObjectThatINeedToAccessFromScript so I can call initialize on it within my jsx file. Eg trough an import TheObjectThatINeedToAccessFromScript from "some-where";
How can I do an import on this script?
It is now available on the window object, so I can access it trough there:
window.TheObjectThatINeedToAccessFromScript.initialize(settings);
If I understand correctly, you just want to export your initialize() function from the script, and import it within your component. E.g:
function initialize() {
/*[...]*/
}
export { initialize };
import React, { useEffect } from 'react';
import { initialize } from './yourScriptFile';
/*[...]*/
function MyComponent() {
useEffect(() => {
initialize();
}, []);
return <div>{/*[...]*/}</div>
}
Unless it's absolutely vital, as a best practice, try to avoid binding things to the window. It can end in tears.

Javascript working before page rendering in Gatsby

I try to convert a HTML template (Bootstrap 5) to Gatsby template. CSS and pages working expected but in HTML template there is a main.js file and it need to load after page rendered.
I modify the main.js file like that;
import { Swiper } from "swiper/swiper-react.cjs.js";
import GLightbox from "glightbox/dist/js/glightbox.min.js";
import AOS from "aos";
AOS.init();
export const onClientEntry = () => {
window.onload = () => {
console.log("deneme");
/*rest of code*/
};
};
In here I try two way. One of them, I create main.js file inside src->components->assets->js folder. Then in layout.js I try to import that file.
import React from "react";
import PropTypes from "prop-types";
import { Breadcrumb } from "gatsby-plugin-breadcrumb";
import Header from "./partials/header";
import { Helmet } from "react-helmet";
import useSiteMetadata from "./hooks/siteMetadata";
import "./assets/css/style.css";
import "./assets/js/main.js"
However in here in debug not hit the any method inside onClientEntry. So I decide to change my way.
Secondly, I try to add code inside main.js to gatsby-browser.js. That's time again getting Cannot read property 'addEventListener' of null because of html is not ready yet.
My file structure:
window (and other global objects like document) are not available during the SSR (Server-Side Rendering) because this action is performed by the Node server (where for obvious reasons there's no window, yet) so you can't access directly to onload function. In addition, accessing these global objects outside the scope of React (without hooks) can potentially break React's hydration process.
That said, you have a few approaches:
Using React hooks. Specifically, useEffect with empty dependencies ([]) fits your specifications, since the effect will be fired once the DOM tree is loaded (that's what empty deps means):
const Layout = ({children}) => {
useEffect(()=>{
mainJs();
}, [])
return <main>{children}</main>
}
Assuming that your ./assets/js/main.js file has a mainJs() function exported, this approach will load it when the DOM tree is loaded. For example:
const mainJs= ()=> console.log("deneme");
The console.log() will be triggered when the HTML tree is built by the browser. Tweak it to adapt it to your needs.
Adding a window-availability condition like:
export const onClientEntry = () => {
if(typeof window !== 'undefined'){
window.onload = () => {
console.log("deneme");
/*rest of code*/
};
}
};
Alternatively, you can output the console.log directly in your onClientEntry, depending on your needs:
export const onClientEntry = () => {
console.log("deneme");
/*rest of code*/
};
You can even combine both approaches by adding a useEffect in your gatsby-browser if it works for you.

Understanding imports from component pages in NextJS

I am new to the Next.JS framework and do not fully understand the logic of importing data from a component page. For example, I have created a page like example.js in my components folder, where I am running an API that pulls data to a graph. Then, inside my index.js file, where I want my graph to be displayed, it is giving me a 'Unhandled Runtime Error', because the API function is not being triggered inside of example.js.
This is how I am importing the page in /pages/index.js:
import dynamic from 'next/dynamic'
const Example = dynamic(() => import('../pages/example.js'))
//Later on inside of my return
<Example />
And from my example.js page I am exporting as follows:
function Example(props) {
return <div>
... my code
}
Then below that, I have the following inside my example.js:
export default Example
Followed by my getStaticProps function:
export async function getStaticProps() {
const res = await fetch("")
const data = await res.json()
if (!data) {
return {
notFound: true
}
}
return {
props: {
data: data.reverse(),
},
}
}
If I use this exact code on my index.js it functions properly. Any ideas as to why this doesn't run, and any solutions as to a fix?
Edit for clarity: My getStaticProps function is in my example.js file, and my issue is that it is not being triggered.
Next.js does a lot of magic to handle server-side rendering, static-site generation and code splitting, and it does it all from within the /pages folder. It's important to know that components within the /pages folder work a little differently from a regular React component. Here are some tips for you that should solve your problem.
1. Put your page components inside the /pages folder.
Components inside this folder can have a default export (the component), along with named exports (the additional Next.js-specific functions like getStaticProps). If Next.js finds the exported functions, it will run them.
2. Use next/dynamic only for dynamic imports.
Dynamic imports are lazy-loaded -- this will affect Next.js' automatic code-splitting and potentially server-side rendering. I don't know the exact inner workings, but my guess is dynamically-loading a Page component (which are special in Next) is probably what's breaking things. I tend to only use dynamic imports when I have a 3rd-party component that breaks SSR, so I need to dynamically import it so it's not server-side rendered.
The simplest way to handle this is probably:
in /pages/index.js:
import Example from '../path/to/example';
export default (props) => (
<div>
<p>My page component</p>
<Example />
</div>
);
export async function getStaticProps() {
return {
props: {
data: // ...
}
};
}
With this solution above, you can use dynamic imports as well -- what matters is that you have your Next.js-specific function exported from the file in /pages immediately:
in /pages/index.js:
import dynamic from 'next/dynamic';
const Example = dynamic(() => import('../components/example'));
export default (props) => (
<div>
<p>My page component</p>
<Example />
</div>
);
export async function getStaticProps() {
return {
props: {
data: // ...
}
};
}
Update to solve "My getStaticProps function is in my example.js file, and my issue is that it is not being triggered.":
Next.js will only call a getStaticProps function that is exported from a file in the /pages directory. If you want to define your getStaticProps method from another directory, you can do that, but you need to import it into /pages and then re-export it like so:
in /components/example.js:
export default () => (
// ... component ...
);
export function getStaticProps() {
// ... function ...
};
and in /pages/index.js:
import Example, { getStaticProps } from '../components/example';
export default Example;
export {
getStaticProps
}
This should work. However, you can't import Example using dynamic imports in this case.

Categories