Next js static export unstyled html flash. [FOUC] - javascript

I am working with next.js and everything seems to be fine when I am using app with npm run dev.
But when I am exporting my next.js app to a static files with command npm run build and trying to open my project for part of the second the screen is unstyled, this cause verry bad user experience.
I know this is called FOUC but how to avoid it on next.js static export?
P.S I am using styled-components library, not sure if that affecting the final result.

If you are using styled-components, I would look into modifying your _document.js file per these instructions: https://styled-components.com/docs/advanced#server-side-rendering
That's how I managed to get out FOUC after trying a bunch of random stuff like:
putting a loader
putting a <script>0</script> tag
etc..

Long shot that this is a fix for anyone else, but I saw this behavior when I mistakenly wrapped the entire page contents inside the head tag. Heh, oops.
So don't override Document with something like this like I did, make sure to close that head tag sooner:
<Html lang="en">
<Head>
<body>
<Main />
<NextScript />
</body>
</Head>
</Html>

For anyone facing this annoying problem using styled-component, I solved it by referring to this and this to render your styling on the server side before your page loads. Hope this helps someone end their frustration
import Document, { Html, Head, Main, NextScript, DocumentContext } from 'next/document';
import { ServerStyleSheet } from 'styled-components';
export default class MyDocument extends Document {
static async getInitialProps(ctx: DocumentContext) {
const sheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage;
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) => sheet.collectStyles(<App {...props} />),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
styles: [initialProps.styles, sheet.getStyleElement()],
};
} finally {
sheet.seal();
}
}
render() {
return (
<Html lang="en">
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}

Related

How to load Google Tag Manager with next/script component (Next.js 11)?

Next.js v11 released a new Script component which has different strategies.
It is recommended to load Google TagManager with afterInteractive strategy.
I've tried
// _app.js
import Script from 'next/script';
class MyApp extends App {
public render() {
const { Component, pageProps } = this.props;
return (
<>
<Script strategy="afterInteractive">
{`(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':` +
`new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],` +
`j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=` +
`'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);` +
`})(window,document,'script','dataLayer','GTM-XXXXXXX');`}
</Script>
<Component {...pageProps} />
</>
);
}
}
export default MyApp;
It works fine, it loads google tag manager, but the problem is that it injects the same script on every page nav, which makes duplicate tags.
How to utilize the new Script component?
You must set an id to your <Script> component because it has inline content (no src attribute).
Next can check if it has been already rendered or not and you will not have these duplications.
This is the eslint rule associated from Next :
next/script components with inline content require an id attribute to be defined to track and optimize the script.
See: https://nextjs.org/docs/messages/inline-script-id
So your solution could be :
<Script id="gtm-script" strategy="afterInteractive">{`...`}</Script>
You should probably also install eslint for your next project :
Either run next lint or install eslint next config :
yarn add -D eslint eslint-config-next
and define the file .eslintrc.json with this content at least :
{
"extends": ["next/core-web-vitals"]
}
Information about next.js eslint configuration.
My final solution was to break apart the GTM script.
Putting the initial dataLayer object on the window in _document page.
// _document.js
import Document, { Head, Html, Main, NextScript } from 'next/document';
export default class MyDocument extends Document {
render() {
return (
<Html lang="en">
<Head>
<meta charSet="utf-8" />
<script
dangerouslySetInnerHTML={{
__html:
`(function(w,l){` +
`w[l] = w[l] || [];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});` +
`})(window,'dataLayer');`,
}}
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
Loading the GMT script with Script component (which is not allowed to be used in the _document page)
// _app.js
import Script from 'next/script';
class MyApp extends App {
public render() {
const { Component, pageProps } = this.props;
return (
<>
<Script src={`https://www.googletagmanager.com/gtm.js?id=GMT-XXXXXXX`} />
<Component {...pageProps} />
</>
);
}
}
export default MyApp;
Your inline scripts require an "id" parameter, so that Next can internally check and avoid loading the scripts again.
The documentation mentioned it but they missed this in the first release.
It was later added as a patch so upgrading your Next would solve this issue
Patch - https://github.com/vercel/next.js/pull/28779/files/11cdc1d28e76c78a140d9abd2e2fb10fc2030b82
Discussion Thread - https://github.com/vercel/next.js/pull/28779

How to use mailerlite popups in a Next.js application

I'm integrating a mailerlite popup for a client's next.js project, and I'm having a difficult time converting the JavaScript snippets into the jsx required to make the popups function properly. On first load it seems to work just fine, but on relaod I'm getting the following error.
window is not defined
I've encountered the issue while dealing with DOM manipulation, but in this case, judging from the code in the snippet, I need the window object.
Install the following snippet of Javascript on every page of your website right before the closing tag.You only need to add this snippet once, even if you plan to have a few different webforms.
<!-- MailerLite Universal -->
<script>
(function(m,a,i,l,e,r){ m['MailerLiteObject']=e;function f(){
var c={ a:arguments,q:[]};var r=this.push(c);return "number"!=typeof r?r:f.bind(c.q);}
f.q=f.q||[];m[e]=m[e]||f.bind(f.q);m[e].q=m[e].q||f.q;r=a.createElement(i);
var _=a.getElementsByTagName(i)[0];r.async=1;r.src=l+'?v'+(~~(new Date().getTime()/1000000));
_.parentNode.insertBefore(r,_);})(window, document, 'script', 'https://static.mailerlite.com/js/universal.js', 'ml');
var ml_account = ml('accounts', '912433', 'd5p1f7l9g0', 'load');
</script>
<!-- End MailerLite Universal -->
I've placed this code in my Layout wrapper. As previously stated, it works fine on first load, but as soon as the user navigates to a new page above error shows up.
PS I found an old question regarding this topic here, but it's old and not quite relevant to my situation. I need to figure out how to convert the above snippet for nextjs. Any help at all would be appreciated.
This approach treats the MailerLite universal tag as its own <script> hosted on your site's domain.
Add a NextJS custom document.
Create a JavaScript file containing the MailerLite universal tag code in ./public. I put mine in ./public/scripts/ml.js.
Add a <script> tag loading #2 in your custom _document.js file:
import Document, { Html, Head, Main, NextScript } from 'next/document'
class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps }
}
render() {
return (
<Html>
<Head>
<script async src="/scripts/ml.js"></script>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default MyDocument
Everything worked as intended from there! (Caveat: I'm only using embedded forms).

How to get URL params when using Custom Document in NextJS

I am using NextJS to product SSR pages, and these pages are language-specific. I would like to set the lang property to declare the language of the text.
So far I have:
import Document, { Html, Head, Main, NextScript } from "next/document"
import Router from "next/router"
class MyDocument extends Document {
render() {
const { lanaguage } = Router.router.query
return (
<Html lang={language}>
<Head />
<body className="antialiased text-white text-base font-sans">
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default MyDocument
However, Router.router is always null when using a Custom Document (_document.js). Is there another way to get the URL params/query when using a Custom Document?
I have been able to solve this by using:
import Document, { Html, Head, Main, NextScript } from "next/document"
class MyDocument extends Document {
render() {
const { lanaguage } = this.props.__NEXT_DATA__.query
return (
<Html lang={lanaguage}>
<Head />
<body className="antialiased text-white text-base font-sans">
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default MyDocument
I am unsure if it is bad practice to use this.props.__NEXT_DATA__. However, it does have the information I require.
Since Next.js v10, if you're using the built-in i18n routing, Next will automatically set the lang attribute of the document.
From the documentation:
Since Next.js knows what language the user is visiting it will
automatically add the lang attribute to the <html> tag.
Next.js doesn't know about variants of a page so it's up to you to add
the hreflang meta tags using next/head. You can learn more about
hreflang in the Google Webmasters documentation.

How to include local javascript on a Gatsby page?

I'm a total React newbie and I guess there is something fundamental I don't quite understand here. A default Gatsby page looks like this. Is there a way to use a local .js file somewhat like this?
<script src="../script/script.js"></script>
What I would like to achieve is to have react ignore script.js but still have the client side use it. A default Gatsby page looks like this, is it possible to do somerthing like that there?
import React from "react"
import { Link } from "gatsby"
import Layout from "../components/layout"
import Image from "../components/image"
import SEO from "../components/seo"
const IndexPage = () => (
<Layout>
<SEO title="Home" keywords={[`gatsby`, `application`, `react`]} />
<h1>Hi people</h1>
<p>Welcome to your new Gatsby site.</p>
<p>Now go build something great.</p>
<div style={{ maxWidth: `300px`, marginBottom: `1.45rem` }}>
<Image />
</div>
<Link to="/page-2/">Go to page 2</Link>
</Layout>
)
After several hours of frustration I finally stumbled upon discussion on GitHub that solved this for me. In Gatsby, there is a thing called static folder, for which one use case is including a small script outside of the bundled code.
Anyone else in the same situation, try proceeding as follows:
Create a folder static to the root of your project.
Put your script script.js in the folder static.
Include the script in your react dom with react-helmet.
So in the case of the code I posted in my original question, for instance:
import React from "react"
import Helmet from "react-helmet"
import { withPrefix, Link } from "gatsby"
import Layout from "../components/layout"
import Image from "../components/image"
import SEO from "../components/seo"
const IndexPage = () => (
<Layout>
<Helmet>
<script src={withPrefix('script.js')} type="text/javascript" />
</Helmet>
<SEO title="Home" keywords={[`gatsby`, `application`, `react`]} />
<h1>Hi people</h1>
<p>Welcome to your new Gatsby site.</p>
<p>Now go build something great.</p>
<div style={{ maxWidth: `300px`, marginBottom: `1.45rem` }}>
<Image />
</div>
<Link to="/page-2/">Go to page 2</Link>
</Layout>
)
Notice the imports
import Helmet from "react-helmet"
import { withPrefix, Link } from "gatsby"
and the script element.
<Helmet>
<script src={withPrefix('script.js')} type="text/javascript" />
</Helmet>
This would have saved hours of my time, hopefully this does it for someone else.
There are many ways to add scripts in GatsbyJS...
To execute a script on a specific page
create a stateless ScriptComponent.js file and place it inside your /src folder.
in your ScriptComponent.js use require() to execute the script inside useEffect() like this:
const ScriptComponent = ({
src, // if internal,put a path relative to this component
onScriptLoad = () => {}, // cb
appendToHead = false,
timeoutDuration = 10,
defer = false,
isExternal = false,
}) => {
useEffect(() => {
setTimeout(() => {
if (isExternal) {
const script = document.createElement('script');
script.src = src;
script.onload = onScriptLoad;
defer
? script.defer = true
: script.async = true;
appendToHead
? document.head.appendChild(script)
: document.body.appendChild(script);
} else { // for internal scripts
// This runs the script
const myScript = require(src);
}
}, timeoutDuration);
}, []);
return null;
};
To run it on client-side, you could check the window object inside your script.js file if you didn't run it in useEffect:
if(typeof window !== 'undefined' && window.document) {
// Your script here...
}
finally, go to the page you want to execute the script in it (e.g. /pages/myPage.js ), and add the component <ScriptComponent />
If you want to execute a script globally in (every component/page) you could use the html.js file.
first, you'll have to extract the file (in case you didn't) by running:
cp .cache/default-html.js src/html.js
inside your html.js file:
<script dangerouslySetInnerHTML= {{ __html:`
// your script here...
// or you could also reuse the same approach as in useEffect above
`}} />
Just create gatsby-ssr.js file on root folder
and add the following pattern for your scripts folder
import React from 'react'
export const onRenderBody = ({ setPostBodyComponents }) => {
setPostBodyComponents([
<script
key="https://code.jquery.com/jquery-3.2.1.slim.min.js"
src="https://code.jquery.com/jquery-3.2.1.slim.min.js"
integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN"
crossOrigin="anonymous"
defer
/>,
<script
key="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"
src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"
integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q"
crossOrigin="anonymous"
defer
/>,
<script
key="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"
src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"
integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl"
crossOrigin="anonymous"
defer
/>
])
}
Then, you at the end of dom you'll see the links to scripts
If you'd like to use a Gatsby plugin, which to me is no different from using an external library like Helmet (plugins are npm packages after all), you could use gatsby-plugin-load-script.
You can provide either the url to the src attribute or a local path. If you're going to store your JS in a local file such as some-minified-js.min.js - make sure to store in the static directory at the root of your project.
Once you do this, you can access via the global object:
global.<object or func name here>
For example, I was trying to include a very small JS library via a minified file, so I stored the file in /static/my-minified-library.min.js and then:
Installed the plugin: npm i --save gatsby-plugin-load-script
Added this to my gatsby-config.js
plugins: [
{
resolve: "gatsby-plugin-load-script",
options: {
src: "/my-minified-library.min.js",
},
},
],
Accessed in my react component like so:
useEffect(() => {
const x = new global.MyImportedLibraryObject();
}, []}
Gatsby uses html.js in the src folder. Not index.html like most react projects.
Example html.js file:
import React from "react"
import PropTypes from "prop-types"
export default class HTML extends React.Component {
render() {
return (
<html {...this.props.htmlAttributes}>
<head>
<meta charSet="utf-8" />
<meta httpEquiv="x-ua-compatible" content="ie=edge" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
{this.props.headComponents}
</head>
<body {...this.props.bodyAttributes}>
{this.props.preBodyComponents}
<div
key={`body`}
id="___gatsby"
dangerouslySetInnerHTML={{ __html: this.props.body }}
/>
{this.props.postBodyComponents}
</body>
</html>
)
}
}
HTML.propTypes = {
htmlAttributes: PropTypes.object,
headComponents: PropTypes.array,
bodyAttributes: PropTypes.object,
preBodyComponents: PropTypes.array,
body: PropTypes.string,
postBodyComponents: PropTypes.array,
}
For adding custom Javascript using dangerouslySetInnerHTML inside src/html.js:
<script
dangerouslySetInnerHTML={{
__html: `
var name = 'world';
console.log('Hello ' + name);
`,
}}
/>
You can try adding your js there but, note that your js may not work as expected. You can always look into react-helmet for more dynamic apps and adding scripts to <head>.
Gatsby Documentation: https://www.gatsbyjs.org/docs/custom-html/
You can do this very easily with the Gatsby plugin "gatsby-plugin-load-script."
Simply do this:
Create a folder named static at the root of your gatsby app
Place your script in it
Add the following configuration in gatsby-config.js
{
resolve: 'gatsby-plugin-load-script',
options: {
src: '/test-script.js', // Change to the script filename
},
},
I'm not sure if anyone still needs this answer, but here it goes:
The answer by Elliot Marques is excellent. If you need it for a local file, upload the script to Github and use a service like JSDelivr. It saves a lot of time and stress.
React works with dynamic DOM. But for rendering it by browser, your web server should send a static index page, where React will be included as another script tag.
So, take a look on your index.html page, which you can find in public folder. There you could insert your script tag in the header section, for example.

React component rendering on server-side, but client-side does not take over

I have an EJS view which is served up to a client:
index.ejs
<!DOCTYPE html>
<html>
<head>
<title>Example app</title>
</head>
<body>
<div id="reactApp">
<%- reactContent %>
</div>
</body>
<script src="__res__/client/main.bundle.js" />
</html>
main.bundle.js is the bundle that I create using browserify:
gulpfile.js (partial)
function bundle(filename) {
var bundler = browserify('./app/client/' + filename + '.js');
bundler.transform(babelify);
bundler.transform(reactify);
return bundler.bundle()
.pipe(source(filename + '.bundle.js'))
.pipe(buffer())
.pipe(uglify())
.pipe(gulp.dest('./dist/client'));
}
And the client is ultimately served this code:
main.js (bundled into main.bundle.js)
import React from 'react';
import {Login} from './auth/login.react';
React.render(React.createElement(Login), document.getElementById('reactApp'));
alert('hi');
However, even though the browser requests and recieves the main.bundle.js script, the client does not run the alert('hi'); line, which leads me to believe that the React.render line does not work either. I can affirm that Javascript is enabled, and my browser is the latest version of Chrome. My react component (Login) is as follows:
login.react.js
import React from 'react';
export class Login extends React.Component
{
handleClick() {
alert('Hello!');
}
render() {
console.log('rendered');
return (
<button onClick={this.handleClick}>This is a React component</button>
);
}
}
So, very simple. However, none of the alerts that you see in the code is ever run. The console.log('rendered'); line is never run on the server, but when I check the source code for my page, I get:
HTML output of my page
<!DOCTYPE html>
<html>
<head>
<title>Example app</title>
</head>
<body>
<div id="reactApp">
<button data-reactid=".2fqdv331erk" data-react-checksum="436409093">Lel fuck u</button>
</div>
</body>
<script src="__res__/client/main.bundle.js" />
</html>
Which means that the server correctly renders my react component, but why does the client script not work? The handleClick function never runs, my console.log lines never run, and neither does the alert lines. What is happening? The reactid and checksum are rendered correctly, shouldn't it be working? Shouldn't the React code on the client-side be smart enough to find the component and run correctly?
In your index.ejs, adding a closing element to your script tag should fix the issue: <script src="__res__/client/main.bundle.js"></script>
On a separate note, in my testing, I was getting an error in login.react.js when loading the page until I added default to the export line: export default class Login extends React.Component. Not sure if you will need to do the same.

Categories

Resources