I am trying to load some scripts in my NextJS application. I have followed the procedures outlined in the NextJS documentation but it doesn't seem to work in my application.
The link to the documentation is, https://nextjs.org/docs/basic-features/script.
I load the scripts in the _document.js file like this.
_document.js
import Document, { Html, Head, Main, NextScript } from "next/document";
import Script from "next/script";
class MyDocument extends Document {
static async getInitialProps(ctx) {
const originalRenderPage = ctx.renderPage;
// Run the React rendering logic synchronously
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>
<Head />
<body>
<Main />
<NextScript />
<Script
src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"
id={`jquery-1${+new Date()}`}
strategy="beforeInteractive"
></Script>
<Script
src="/webflow.js"
id={`webflow-1${+new Date()}`}
strategy="beforeInteractive"
></Script>
<Script
src="https://ajax.googleapis.com/ajax/libs/webfont/1.6.26/webfont.js"
id={`webfont-1${+new Date()}`}
strategy="beforeInteractive"
></Script>
<Script strategy="lazyOnload" id={`load-font-1${+new Date()}`}>
{`WebFont.load({
google: {
families: ["DM Sans:regular,500,700"]
}})`}
</Script>
</body>
</Html>
);
}
}
export default MyDocument;
I also tried this by putting the scripts in the Head tag but it was the same result.
_document.js
import Document, { Html, Head, Main, NextScript } from "next/document";
import Script from "next/script";
class MyDocument extends Document {
static async getInitialProps(ctx) {
const originalRenderPage = ctx.renderPage;
// Run the React rendering logic synchronously
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>
<Head>
<Script
src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"
id={`jquery-1${+new Date()}`}
strategy="beforeInteractive"
></Script>
<Script
src="/webflow.js"
id={`webflow-1${+new Date()}`}
strategy="beforeInteractive"
></Script>
<Script
src="https://ajax.googleapis.com/ajax/libs/webfont/1.6.26/webfont.js"
id={`webfont-1${+new Date()}`}
strategy="beforeInteractive"
></Script>
<Script strategy="lazyOnload" id={`load-font-1${+new Date()}`}>
{`WebFont.load({
google: {
families: ["DM Sans:regular,500,700"]
}})`}
</Script>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
export default MyDocument;
I confirmed that the scripts were not working by checking the network tab and also as there functionalities that were meant to work on the website and dependent on the scripts were not working.
Related
I am struggling to collect custom Simple Analytics metadata in my Next.js app. Looking at their docs, I can either set metadata on the window object (link) or add it via a callback function (link).
My Next.js app looks as follows:
_document.js
import Document, { Html, Head, Main, NextScript } from 'next/document'
export default class MyDocument extends Document {
render() {
return (
<Html lang="en">
<Head>
[...snip...]
<script dangerouslySetInnerHTML={{
__html: `window.sa_event=window.sa_event||function(){a=[].slice.call(arguments);sa_event.q?sa_event.q.push(a):sa_event.q=[a]};`
}}/>
</Head>
<body>
<Main />
<NextScript />
// NOTE 3: Where can I define `func` so I have access to the router/query string params?
<script async defer data-metadata-collector="func" src="https://scripts.simpleanalyticscdn.com/latest.js"></script>
<noscript><img src="https://queue.simpleanalyticscdn.com/noscript.gif" alt=""/></noscript>
</body>
</Html>
)
}
}
page.js
import Head from 'next/head'
import { useRouter } from 'next/router'
import Layout from '../components/layout'
export default function Page() {
const router = useRouter()
const i = router.query.i
return (
<>
<Head>
[...snip...]
</Head>
// NOTE 1: This does not work
<script>
sa_metadata = { i: i }
</script>
// NOTE 2: I cannot access `i` here
<script dangerouslySetInnerHTML={{
__html: `window.sa_metadata={ i: i };`
}}/>
[...snip...]
</>
)
}
As you can see, I tried two ways of setting metadata on window (NOTES 1 and 2) and I got stuck on the callback function (NOTE 3). Would appreciate any help in moving this forward.
I'm trying to follow this official React Documentation on how to add React to a website.
In file main.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Add React in One Minute</title>
</head>
<body>
<div id="root"></div>
<!-- Load React. -->
<!-- Note: when deploying, replace "development.js" with "production.min.js". -->
<script src="https://unpkg.com/react#18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom#18/umd/react-dom.development.js" crossorigin></script>
<!-- Load our React component. -->
<script src = "states_clock.js"> </script>
</body>
</html>
In file states_clock.js
// states_clock.js
'use strict';
const domContainer = document.getElementById('root');
const root = ReactDOM.createRoot(domContainer);
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
function tick() {
root.render(<Clock date={new Date()} />);
}
setInterval(tick, 1000);
Both files are in the same folder.
When I open the html page in chrome, I get the error message:
Uncaught SyntaxError: Unexpected token '<' (at states_clock.js:11:7)
The < being complained about is that of the div in the js file.
This:
class Clock extends React.Component {
render() {
return (
<div>
is not JavaScript syntax - it's JSX syntax.
When you do
<script src = "states_clock.js"> </script>
as you would with any normal script tag, you're telling the browser to interpret and run it as a standard JavaScript file, which doesn't work, because it isn't. Add the attribute type="text/babel" to the script tag so it doesn't get run as JavaScript, and so that Babel Standalone sees that it's a script tag for it to process.
<script src="states_clock.js" type="text/babel"></script>
You could also write the JSX inline, like this:
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id='root'></div>
<script src="https://unpkg.com/#babel/standalone/babel.min.js"></script>
<script type="text/babel">
'use strict';
const domContainer = document.getElementById('root');
const root = ReactDOM.createRoot(domContainer);
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
function tick() {
root.render(<Clock date={new Date()} />);
}
setInterval(tick, 1000);
</script>
So, thanks to the comment by ChrisG, I understood that we're not supposed to use JSX in this part of the tutorial.
In that spirit, here's my solution:
'use strict';
const e = React.createElement;
const domContainer = document.getElementById('root');
const root = ReactDOM.createRoot(domContainer);
class Clock extends React.Component {
render() {
return e('div', null, e("h1", null, "Hello, world!"),
e("h2", null, "It is ", this.props.date.toLocaleTimeString(), "."));
};
}
function tick() {
root.render(e(Clock, {date: new Date()}, null))
}
setInterval(tick, 1000);
P.S.: Here's useful link that transforms JSX code into non-JSX code.
I'm trying to incorporate the Google Sign In feature in my Next app. Here's how I've been doing it.
In _document.js
import React from 'react';
import Document, {Html, Head, Main, NextScript } from 'next/document';
export default class MyDocument extends Document{
render(){
return(
<Html lang="en">
<Head>
<meta name="theme-color" />
{/* This should add `google` to `window` */}
<script type="application/javascript" src="https://accounts.google.com/gsi/client" async />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
And then in pages/login.js
import { React, useEffect, ... } from 'react'
export default function LoginPage (props) {
// When page is rendered, render the 'Sign-in with Google' button
useEffect(() => {
window.google.accounts.id.initialize({
client_id: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID,
callback: res => { console.log(res) }
})
window.google.accounts.id.renderButton(
document.getElementById('googleSignIn'),
{ theme: 'filled_blue', size: 'large', text: 'continue_with' }
)
}, [])
return (<>
{/* Provide an element for the button to render into */}
<div id="googleSignIn" />
</>)
}
But this throws an error:
login.js:48 Uncaught TypeError: Cannot read properties of undefined (reading 'accounts')
In other words, window.google is not defined.
What's wrong with this?
You can remove the async attribute from the script to ensure it gets loaded synchronously as early as possible.
<script type="application/javascript" src="https://accounts.google.com/gsi/client" />
Alternatively, you can also use the next/script component with the beforeInteractive strategy to achieve a similar behaviour.
import Script from 'next/script'
<Script type="application/javascript" src="https://accounts.google.com/gsi/client" strategy="beforeInteractive" />
NextJS always prerender pages on server, in this case window is unavailable. You can always use next/router library. to wait until page loads on client
import { React, useEffect, ... } from 'react'
import { useRouter } from 'next/router'
export default function LoginPage (props) {
// When page is rendered, render the 'Sign-in with Google' button
const router=useRouter() //create router state
useEffect(() => {
if(window){ //check window if exist on each effect execution
window.google.accounts.id.initialize({
client_id: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID,
callback: res => { console.log(res) }
})
window.google.accounts.id.renderButton(
document.getElementById('googleSignIn'),
{ theme: 'filled_blue', size: 'large', text: 'continue_with' }
)
}
}, [router]) // to run again when client router loads
return (<>
{/* Provide an element for the button to render into */}
<div id="googleSignIn" />
</>)
}
before this line in your code "window.google.accounts.id.initialize({" try using /global google/ and then try to run it.
enter image description here
When my Next JS app is compiled, it generates a list of script files to include like this.
<script src="/_next/static/chunks/main-1234.js" async=""></script>
<script src="/_next/static/chunks/webpack-1234.js" async=""></script>
<script src="/_next/static/chunks/framework.1234.js" async=""></script>
<script src="/_next/static/chunks/1234.5678.js" async=""></script>
<script src="/_next/static/chunks/commons.1234.js" async=""></script>
<script src="/_next/static/chunks/pages/_app-1234.js" async=""></script>
<script src="/_next/static/chunks/1234.5678.js" async=""></script>
<script src="/_next/static/chunks/pages/%5B%5B...path%5D%5D-1234.js" async=""></script>
<script src="/_next/static/1234/_buildManifest.js" async=""></script>
<script src="/_next/static/1234/_ssgManifest.js" async=""></script>
I want to add a custom data attribute to some of them like this.
<script data-cookieconsent="ignore" src="/_next/static/chunks/pages/%5B%5B...path%5D%5D-1234.js" async=""></script>
I've explored trying to do this in the next.config.js file as I know it's possible to make webpack overrides in there, but I'm not seeing a way to add data attributes to dynamically generated js files like this.
From Next.js 11
You'll need to extend and modify getScripts from the Head class in your _document.js file instead.
import Document, { Html, Head, Main, NextScript } from 'next/document'
class CustomHead extends Head {
getScripts(files) {
const originalScripts = super.getScripts(files);
return originalScripts.map((script) => {
return React.cloneElement(script, {
'data-cookieconsent': this.props.cookieconsent
});
});
}
}
class CustomDocument extends Document {
render() {
return (
<Html>
<CustomHead cookieconsent="ignore" />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default CustomDocument
Before Next.js 11
An alternative solution is to extend the NextScript class in your _document.js file, as to include your custom data-* attribute in the scripts generated by Next.js.
import Document, { Html, Head, Main, NextScript } from 'next/document'
class CustomNextScript extends NextScript {
getScripts(files) {
const originalScripts = super.getScripts(files);
return originalScripts.map((script) => {
return React.cloneElement(script, {
'data-cookieconsent': this.props.cookieconsent
});
});
}
}
class CustomDocument extends Document {
render() {
return (
<Html>
<Head />
<body>
<Main />
<CustomNextScript cookieconsent="ignore" />
</body>
</Html>
)
}
}
export default CustomDocument
I'm using Babel-Standalone to use JSX in a React application without using NPM. Babel apparently translates 'import' statements into 'require' statements; importing 'require.js' and other dependencies to make this work produces more errors.
Surely, there must be a simple way to perform an import/export in the context of client-side JSX. Please advise (no Node/NPM/Webpack solutions are sought here; CDN of appropriate library(ies) and rewrite of import statement, etc., are sought).
<!doctype html>
<html lang="en-us">
<head>
<title>React JSX Babel-Standalone Import/Export Problem</title>
<meta charset="utf-8">
</head>
<body>
<div id="root"></div>
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<script type="text/babel">
// See MyExport.js text below this SCRIPT
// Goal: to employ <MyExport /> in the return of App.
// import MyExport from "./MyExport";
const App = () => {
return (
<div>Hello</div>
);
};
ReactDOM.render(<App />, document.querySelector("#root"));
</script>
<!-- MyExport.js:
const MyExport = () => {
return (
<div>MyExport</div>
);
};
export default MyExport;
-->
</body>
</html>
There is a solution: (1) The JSX script containing the export must be included in a SCRIPT element along with the others; it cannot simply be referenced by another script without. (2) Both that JSX script and the JSX script importing from it must have the custom attribute data-plugins="transform-es2015-modules-umd" along with the expected attribute type="text/babel". Run the following HTML, a modification of what was provided in the question, which provides the solution (you'll have to create MyExport.js locally to run it):
<!doctype html>
<html lang="en-us">
<head>
<title>React JSX Babel-Standalone Import/Export Problem</title>
<meta charset="utf-8">
</head>
<body>
<div id="root"></div>
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<script data-plugins="transform-es2015-modules-umd" type="text/babel" src="./MyExport.js"></script>
<script data-plugins="transform-es2015-modules-umd" type="text/babel">
import MyExport from "./MyExport";
const App = () => {
return (
<MyExport/>
);
};
ReactDOM.render(<App />, document.querySelector("#root"));
</script>
<!-- MyExport.js:
const MyExport = () => {
return (
<div>MyExport element is imported!</div>
);
};
export default MyExport;
-->
</body>
</html>
I hope this helps someone else.
you should include first all needed component files, then run the app js file
Example:
<div id="root-react"></div>
<script src="https://unpkg.com/react#16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone#6/babel.min.js" crossorigin></script>
And the file's tree is something like:
js/app.js
js/subcomponent.js
The app.js content is for example:
"use strict";
class MainReact extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<strong>app.js</strong> is loaded<br/>
<SubComponent />
</div>
)
}
}
ReactDOM.render(<MainReact />, document.getElementById("root-react"));
subcomponent.js content:
"use strict";
class SubComponent extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<span> SubComponent-is-working </span>
)
}
}
customElements.define('subcomponent', SubComponent);
The file inclusion in the html file should be:
<script type="text/babel" src="js/subcomponent.js"></script>
<script type="text/babel" src="js/app.js"></script>
Hope it helps somebody.
CodePen demo
Babel isn't a module bundler or a module system implementation, babel is just a transpiler to provide access to the latest JS features that aren't supported in the browser or node.
If you want to use ES Modules without any third parties like webpack, rollup, etc. have a look at https://developers.google.com/web/fundamentals/primers/modules.
You should be able to do something like:
<script type="module">
import MyExport from "./url/to/MyExport.mjs";
const App = () => {
return (
<div>Hello</div>
);
};
ReactDOM.render(<App />, document.querySelector("#root"));
</script>
JS Modules via script tags is only supported in the latest versions of major browsers: https://caniuse.com/#feat=es6-module
Also, you'll need to workaround the fact that babel-standalone needs your script tags to be text/babel EDIT: the workaround is using a data-type="module" tag as well as the type="text/babel" tag: https://babeljs.io/docs/en/babel-standalone#usage