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.
Related
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.
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
We are using NextJS and Material-UI for our site, and upon loading the pages, it gives a FOUC. I've narrowed the problem down to the fact that the JS loads faster than the .css file, so I was wondering if there was a way to preload the .css file? All of our pages use the same .css file which is located under /pages/styles.css
Here is /pages/_app.js if that's any help:
// pages/_app.js
import { Provider } from 'next-auth/client'
import { createMuiTheme } from '#material-ui/core/styles';
import { ThemeProvider } from '#material-ui/styles';
import styles from './styles.css'
import Layout from '../components/layout'
import Head from 'next/head'
const theme = createMuiTheme({
palette: {
primary: {
main: "#2196f3", // blue
},
secondary: {
main: "#d3d3d3", // gray
},
},
});
export default function _App ({ Component, pageProps }) {
return (
<ThemeProvider theme={theme}>
<Provider options={{ clientMaxAge: 0, keepAlive: 0 }} session={pageProps.session}>
<Layout>
{/* Head */}
<Head>
<title>Kevin Support</title>
<link rel="icon" href="/static/favicon.png"/>
</Head>
{/* Page */}
<Component {...pageProps} />
</Layout>
</Provider>
</ThemeProvider>
)
}
Load the CSS file with the <link> element inside the head. The parsing process of the browser will then make sure that the CSS file is loaded before the site content is shown.
In your current approach you load the CSS is loaded with JavaScript, after the FCP has rendered the CSS will be parsed.
You have 2 options to fix this:
You link the CSS file as mentioned above with a <link> element.
You get the text content of the CSS file and set it as the innerHTML of a <style> element.
Perhaps styles weren't applied on the server-side. Try to add _document.js from Material-UI's Next.js example. Adjust it to your needs.
// pages/_document.js
import React from 'react';
import Document, { Html, Head, Main, NextScript } from 'next/document';
import { ServerStyleSheets } from '#material-ui/core/styles';
import theme from '../src/theme';
export default class MyDocument extends Document {
render() {
return (
<Html lang="en">
<Head>
{/* PWA primary color */}
<meta name="theme-color" content={theme.palette.primary.main} />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
// `getInitialProps` belongs to `_document` (instead of `_app`),
// it's compatible with server-side generation (SSG).
MyDocument.getInitialProps = async (ctx) => {
// Resolution order
//
// On the server:
// 1. app.getInitialProps
// 2. page.getInitialProps
// 3. document.getInitialProps
// 4. app.render
// 5. page.render
// 6. document.render
//
// On the server with error:
// 1. document.getInitialProps
// 2. app.render
// 3. page.render
// 4. document.render
//
// On the client
// 1. app.getInitialProps
// 2. page.getInitialProps
// 3. app.render
// 4. page.render
// Render app and page and get the context of the page with collected side effects.
const sheets = new ServerStyleSheets();
const originalRenderPage = ctx.renderPage;
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) => sheets.collect(<App {...props} />),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
// Styles fragment is rendered after the app and page rendering finish.
styles: [...React.Children.toArray(initialProps.styles), sheets.getStyleElement()],
};
};
Also, you can try to remove server-side injected CSS in the _app.js like so (see example):
React.useEffect(() => {
// Remove the server-side injected CSS.
const jssStyles = document.querySelector('#jss-server-side');
if (jssStyles) {
jssStyles.parentElement.removeChild(jssStyles);
}
}, []);
I faced this same issue.
what I did was to make this work was to add this script inside _document.tsx
<script
dangerouslySetInnerHTML={{
__html: `
if(document) {
document.querySelectorAll("link[rel='preload'][as='style']").forEach(link => link.rel = "stylesheet")}
`
}}
/>
I'm taking over a website by V2 Docusaurus.
One particularity of our website is that we need to load office.js and css-vars-ponyfill.min.js, and run some functions in patches.js in the very beginning. So the previous developer decided to use the following approach.
In every .mdx.md page, he wrapped the content by a component MainWrapper:
<MainWrapper>
... ...
Real content
... ...
</MainWrapper>
MainWrapper/index.js is defined as follows
import React from 'react';
import Head from '#docusaurus/Head';
function MainWrapper(props) {
return (<>
<Head>
<script
src="/lib/patches.js"
onload="(function(){console.log('patches.js has been fully loaded')}).call(this)" >
</script>
<script async defer
src='https://unpkg.com/css-vars-ponyfill#2/dist/css-vars-ponyfill.min.js'
onload="(function(){console.log('css-vars-ponyfill.min.js has been fully loaded'); onCssVarsPonyfillLoad();}).call(this)">
</script>
<script async defer
src='https://appsforoffice.microsoft.com/lib/1/hosted/office.js'
onload="(function(){console.log('office.js has been fully loaded'); onOfficejsLoad();}).call(this)">
</script>
</Head>
{props.children}
</>)
}
export default MainWrapper;
lib/Patches.js contains real operations:
var pushStateRef = history.pushState;
var replaceStateRef = history.replaceState;
console.log("already inside patches.js")
console.log("history.pushSate and replaceState have been saved")
function patch() {
if (!history.pushState) {
history.pushState = pushStateRef;
history.replaceState = replaceStateRef;
console.log("history.pushState and replaceState have been set back")
};
}
... ...
function onCssVarsPonyfillLoad() {
console.log("already inside patches.js > onCssVarsPonyfillLoad()")
... ...
}
function onOfficejsLoad() {
Office.onReady(function () {
console.log("already inside Office.onReady");
patch();
})
}
My tests show that, sometimes, this implementation can ensure loading patches.js before office.js and css-vars-ponyfill.min.js as <script async defer do. However, sometimes, this order cannot be ensured:
#docusarus/Head uses react-helmet. Does anyone know how to fix this loading order problem? What I want is loading patches.js before anything else, is there any workaround?
One workaround is to just put the whole code of patches.js inside <script>... ...</script> before the other files as follows:
import React from 'react';
import Head from '#docusaurus/Head';
import CssvarsWrapper from '../CssvarsWrapper';
import OfficejsWrapper from '../OfficejsWrapper';
class MainWrapper extends React.Component {
render() {
return (
<>
<Head>
<script>{`
console.log("begin patches")
... ... // the body of patches.js
`}</script>
<script async defer
src='https://unpkg.com/css-vars-ponyfill#2/dist/css-vars-ponyfill.min.js'
onload="(function(){console.log('css-vars-ponyfill.min.js has been fully loaded'); onCssVarsPonyfillLoad();}).call(this)">
</script>
<script async defer
src='https://appsforoffice.microsoft.com/lib/1/hosted/office.js'
onload="(function(){console.log('office.js has been fully loaded'); onOfficejsLoad();}).call(this)">
</script>
</Head>
{this.props.children}
</>
)
}
}
export default MainWrapper;
I'm trying to insert an inforgram into my project and I have to paste the script and I dont know why it dosen't work.
I have all of my scripts into _app.js and it works fine, but I don't know why it dosen't load this script in particular... I mean, when I inspect the code, I can see the script but dosen't load the graphic.
I already tried to load it into componentDidMount inside _app (and it works) but crashes when I navigate to the site and also try like this:
<script dangerouslySetInnerHTML={{
__html: `
!function(e,i,n,s){var t="InfogramEmbeds",d=e.getElementsByTagName("script")[0];if(window[t]&&window[t].initialized)window[t].process&&window[t].process();else if(!e.getElementById(n)){var o=e.createElement("script");o.async=1,o.id=n,o.src="https://e.infogram.com/js/dist/embed-loader-min.js",d.parentNode.insertBefore(o,d)}}(document,0,"infogram-async")}} />
Thank you for any help.
My structure is:
- pages
- _app.js
- _document.js
- _index.js
-components
- graphic
- index.js
- public
- static
-hello.js ---> this is my script file!
_APP.JS:
import Head from "next/head";
import App from "next/app";
import React from "react";
export default class MyApp extends App {
static async getInitialProps() {
// code
}
render(){
return(
<div>
<Head>
<link href="https://fonts.googleapis.com/css?family=Montserrat|Seymour+One&display=swap" rel="stylesheet" />
<title>En vivo</title>
<script type="text/javascript" src="/static/hello.js"></script> // script to load
</Head>
<Graphic />
</div>
)
}
}
Graphic.js
import React from "react";
function Graphic() {
return (
<>
<div className="infogram-embed" data-id="f86abba0-e624-4ba7-ae51-ac8ab88c1bf7" data-type="interactive" data-title="Untitled dashboard"></div>
</>
);
}
export default Graphic;
Hello.js
!function(e,i,n,s){var t="InfogramEmbeds",d=e.getElementsByTagName("script")[0];if(window[t]&&window[t].initialized)window[t].process&&window[t].process();else if(!e.getElementById(n)){var o=e.createElement("script");o.async=1,o.id=n,o.src="https://e.infogram.com/js/dist/embed-loader-min.js",d.parentNode.insertBefore(o,d)}}(document,0,"infogram-async")
Finally I fond a solution. I have to made changes in my Graphic component like this:
import React { useEffect, useState } from "react";
function Graphic(props) {
const url = props.data;
useEffect(() => {
const script = document.createElement("script");
script.src = !(function(e, i, n, s) {
var t = "InfogramEmbeds",
d = e.getElementsByTagName("script")[0];
if (window[t] && window[t].initialized)
window[t].process && window[t].process();
else if (!e.getElementById(n)) {
var o = e.createElement("script");
(o.async = 1),
(o.id = n),
(o.src = "https://e.infogram.com/js/dist/embed-loader-min.js"),
d.parentNode.insertBefore(o, d);
}
})(document, 0, "infogram-async");
script.async = true;
document.body.appendChild(script);
return () => {
document.body.removeChild(script);
};
}, [url]);
return (
<>
{url.map(el => (
<>
<div
className="infogram-embed"
data-id={el.value}
data-type="interactive"
data-title="Untitled dashboard"
></div>
</>
))}
</>
);
}
export default Graphic;