Skip to content

Instantly share code, notes, and snippets.

@alinzk
Last active January 20, 2022 05:55
Show Gist options
  • Save alinzk/593cbf0e79fa0a54111388a64dd9e480 to your computer and use it in GitHub Desktop.
Save alinzk/593cbf0e79fa0a54111388a64dd9e480 to your computer and use it in GitHub Desktop.
Customized NextScript from next.js to delay loading for non-critical js
/*
NOTE: This modifies next.js internal api behavior and may break your application.
Tested on next.js version: 9.2.1
*/
import React from 'react';
import { compact, flatten } from 'lodash';
import { NextScript } from 'next/document';
class NextScriptCustom extends NextScript {
render() {
const orgNextScripts = flatten(super.render().props.children);
const scripts = compact(
orgNextScripts.map((child) => {
if (child.props.id === '__NEXT_DATA__') {
return {
props: { ...child.props },
content: child.props.dangerouslySetInnerHTML.__html
};
}
if (child?.type === 'script') {
return {
props: { ...child.props },
content: ''
};
}
return null;
})
);
const initialFilterer = props => !props.src || !props.src.includes('chunk');
const initialLoadScripts = scripts.filter(({ props }) => initialFilterer(props));
const chunkedScripts = scripts.filter(({ props }) => !initialFilterer(props));
const jsContent = `
var chunkedScripts = ${JSON.stringify(chunkedScripts)};
setTimeout(() => {
chunkedScripts.map((script) => {
if (!script || !script.props) return;
try {
var scriptTag = document.createElement('script');
scriptTag.src = script.props.src;
scriptTag.async = script.props.async;
scriptTag.defer = script.props.defer;
if (script.props.id) scriptTag.id = script.props.id;
if (script.content) scriptTag.innerHTML = script.content;
document.body.appendChild(scriptTag);
}
catch(err) {
console.log(err);
}
});
// 1800ms seems like when PageSpeed Insights stop waiting for more js
}, 1800);
`;
return (
<>
{initialLoadScripts.map(({ props }) => (
<script key={props.id} {...props} src={props.src} />
))}
<script id="__NEXT_SCRIPT_CUSTOM" defer dangerouslySetInnerHTML={{ __html: jsContent }} />
</>
);
}
}
export default NextScriptCustom;
@kartikag01
Copy link

kartikag01 commented Sep 29, 2020

@anawaz42 this is a great solution for SSR pages, with this our page score directly goes to 95+.
Shouldn't it impact react time user performance or TTI, any idea around this?

@kartikag01
Copy link

@anawaz42 why don't to load scripts in window.onload instead of setTimeout of 1800ms?

@alinzk
Copy link
Author

alinzk commented Sep 30, 2020

Hi @kartikag01, glad to know that this helped you. And yes, it will affect user interactions, for my team we added script to enable important interactions instantly, but other stuff takes a bit of time (1800ms) as you noted in the other comment.

window.onload can be longer than 1800ms in some cases (slow network etc.) but after multiple tests my team came up with this number that doesn't affect the UX much and also makes the page speed test happy.

@dipakkr
Copy link

dipakkr commented Mar 22, 2021

Hi @anawaz42, I am getting an error props not defined in NextScriptCustom.js

I am not sure what props to pass from _document.js.

Can you share your _document.js file code ?

@tiagobbraga
Copy link

Hi @dipakkr, try this:

`
import React from "react";
import {compact, flatten} from "lodash";

import {NextScript} from "next/document";

class NextScriptCustom extends NextScript {
render() {
const orgNextScripts = compact(flatten(super.render().props.children));

const scripts = compact(
  orgNextScripts.map((child) => {
    if (child.props.id === "__NEXT_DATA__") {
      return {
        props: {...child.props},
        content: child.props.dangerouslySetInnerHTML.__html,
      };
    }

    if (child.type === "script") {
      return {
        props: {...child.props},
        content: "",
      };
    }

    return null;
  }),
);

const initialFilterer = (props) => !props.src || !props.src.includes("chunk");

const initialLoadScripts = scripts.filter((item) => initialFilterer(item.props));
const chunkedScripts = scripts.filter((item) => !initialFilterer(item.props));

const jsContent = `
  var chunkedScripts = ${JSON.stringify(chunkedScripts)};
  setTimeout(() => {
    chunkedScripts.map((script) => {
      if (!script || !script.props) return;

      try {
        var scriptTag = document.createElement('script');

        scriptTag.src = script.props.src;
        scriptTag.async = script.props.async;
        scriptTag.defer = script.props.defer;
        
        if (script.props.id) scriptTag.id = script.props.id;
        if (script.content) scriptTag.innerHTML = script.content;
        document.body.appendChild(scriptTag);
      }
      catch(err) {
        console.log(err);
      }
    });
  // 1800ms seems like when PageSpeed Insights stop waiting for more js       
  }, 1800);
`;

return (
  <>
    {initialLoadScripts.map(({props}, index) => (
      <script key={index} {...props} src={props.src} />
    ))}

    <script id="__NEXT_SCRIPT_CUSTOM" defer dangerouslySetInnerHTML={{__html: jsContent}} />
  </>
);

}
}

export default NextScriptCustom;
`

My version of the next.js is v9.3.5

@speeday
Copy link

speeday commented May 26, 2021

Hi @anawaz42,

Can we add Next-AMP tag in between this code to get pass in AMP VALIDATOR online..?

Next-AMP Tags:- https://nextjs.org/docs/api-reference/next/amp
AMP Validator: - https://search.google.com/test/amp

As of now it get reported as error in AMP Validator.

@speeday
Copy link

speeday commented Jun 7, 2021

Hello All,

I have been using it but found that it is been avoided by Google Insights Page Speed New Update v8.0.0.

Anyone if used and passed Google Page Speed with Good Score. Please let me know.

@speeday
Copy link

speeday commented Aug 24, 2021

Hello @anawaz42,

Hope you noted above comments for AMP and Google Page Speed v8.0.0 with Next JS v11.0.0 to share your inputs for the same..!!

@jaintarun268
Copy link

jaintarun268 commented Jan 20, 2022

I am facing same errors.

Error occurred prerendering page "/nextScriptCustom". Read more: https://nextjs.org/docs/messages/prerender-error
TypeError: Cannot destructure property 'assetPrefix' of 'this.context' as it is null.
at NextScriptCustom.render (G:\Projects\ArkaSoftwares\React\arka-react.next\server\chunks\6859.js:880:17)
at NextScriptCustom.render (G:\Projects\ArkaSoftwares\React\arka-react.next\server\pages\nextScriptCustom.js:34:82)
at d (G:\Projects\ArkaSoftwares\React\arka-react\node_modules\react-dom\cjs\react-dom-server.node.production.min.js:35:231)
at bb (G:\Projects\ArkaSoftwares\React\arka-react\node_modules\react-dom\cjs\react-dom-server.node.production.min.js:36:16)
at a.b.render (G:\Projects\ArkaSoftwares\React\arka-react\node_modules\react-dom\cjs\react-dom-server.node.production.min.js:42:43)
at a.b.read (G:\Projects\ArkaSoftwares\React\arka-react\node_modules\react-dom\cjs\react-dom-server.node.production.min.js:41:83)
at Object.exports.renderToString (G:\Projects\ArkaSoftwares\React\arka-react\node_modules\react-dom\cjs\react-dom-server.node.production.min.js:52:138)
at Object.renderPage (G:\Projects\ArkaSoftwares\React\arka-react\node_modules\next\dist\server\render.js:686:46)
at Object.defaultGetInitialProps (G:\Projects\ArkaSoftwares\React\arka-react\node_modules\next\dist\server\render.js:316:51)
at Function.getInitialProps (G:\Projects\ArkaSoftwares\React\arka-react.next\server\chunks\6859.js:515:20)
[== ] info - Generating static pages (182/426)
Error occurred prerendering page "/ar/nextScriptCustom". Read more: https://nextjs.org/docs/messages/prerender-error
TypeError: Cannot destructure property 'assetPrefix' of 'this.context' as it is null.
at NextScriptCustom.render (G:\Projects\ArkaSoftwares\React\arka-react.next\server\chunks\6859.js:880:17)
at NextScriptCustom.render (G:\Projects\ArkaSoftwares\React\arka-react.next\server\pages\nextScriptCustom.js:34:82)
at d (G:\Projects\ArkaSoftwares\React\arka-react\node_modules\react-dom\cjs\react-dom-server.node.production.min.js:35:231)
at bb (G:\Projects\ArkaSoftwares\React\arka-react\node_modules\react-dom\cjs\react-dom-server.node.production.min.js:36:16)
at a.b.render (G:\Projects\ArkaSoftwares\React\arka-react\node_modules\react-dom\cjs\react-dom-server.node.production.min.js:42:43)
at a.b.read (G:\Projects\ArkaSoftwares\React\arka-react\node_modules\react-dom\cjs\react-dom-server.node.production.min.js:41:83)
at Object.exports.renderToString (G:\Projects\ArkaSoftwares\React\arka-react\node_modules\react-dom\cjs\react-dom-server.node.production.min.js:52:138)
at Object.renderPage (G:\Projects\ArkaSoftwares\React\arka-react\node_modules\next\dist\server\render.js:686:46)
at Object.defaultGetInitialProps (G:\Projects\ArkaSoftwares\React\arka-react\node_modules\next\dist\server\render.js:316:51)
at Function.getInitialProps (G:\Projects\ArkaSoftwares\React\arka-react.next\server\chunks\6859.js:515:20)

Error occurred prerendering page "/en/nextScriptCustom". Read more: https://nextjs.org/docs/messages/prerender-error
TypeError: Cannot destructure property 'assetPrefix' of 'this.context' as it is null.
at NextScriptCustom.render (G:\Projects\ArkaSoftwares\React\arka-react.next\server\chunks\6859.js:880:17)
at NextScriptCustom.render (G:\Projects\ArkaSoftwares\React\arka-react.next\server\pages\nextScriptCustom.js:34:82)
at d (G:\Projects\ArkaSoftwares\React\arka-react\node_modules\react-dom\cjs\react-dom-server.node.production.min.js:35:231)
at bb (G:\Projects\ArkaSoftwares\React\arka-react\node_modules\react-dom\cjs\react-dom-server.node.production.min.js:36:16)
at a.b.render (G:\Projects\ArkaSoftwares\React\arka-react\node_modules\react-dom\cjs\react-dom-server.node.production.min.js:42:43)
at a.b.read (G:\Projects\ArkaSoftwares\React\arka-react\node_modules\react-dom\cjs\react-dom-server.node.production.min.js:41:83)
at Object.exports.renderToString (G:\Projects\ArkaSoftwares\React\arka-react\node_modules\react-dom\cjs\react-dom-server.node.production.min.js:52:138)
at Object.renderPage (G:\Projects\ArkaSoftwares\React\arka-react\node_modules\next\dist\server\render.js:686:46)
at Object.defaultGetInitialProps (G:\Projects\ArkaSoftwares\React\arka-react\node_modules\next\dist\server\render.js:316:51)
at Function.getInitialProps (G:\Projects\ArkaSoftwares\React\arka-react.next\server\chunks\6859.js:515:20)
info - Generating static pages (426/426)

Build error occurred
Error: Export encountered errors on following paths:
/nextScriptCustom
/nextScriptCustom: /ar/nextScriptCustom
/nextScriptCustom: /en/nextScriptCustom
at G:\Projects\ArkaSoftwares\React\arka-react\node_modules\next\dist\export\index.js:500:19
at runMicrotasks ()
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async Span.traceAsyncFn (G:\Projects\ArkaSoftwares\React\arka-react\node_modules\next\dist\trace\trace.js:74:20)
at async G:\Projects\ArkaSoftwares\React\arka-react\node_modules\next\dist\build\index.js:987:17
at async Span.traceAsyncFn (G:\Projects\ArkaSoftwares\React\arka-react\node_modules\next\dist\trace\trace.js:74:20)
at async G:\Projects\ArkaSoftwares\React\arka-react\node_modules\next\dist\build\index.js:861:13
at async Span.traceAsyncFn (G:\Projects\ArkaSoftwares\React\arka-react\node_modules\next\dist\trace\trace.js:74:20)
at async Object.build [as default] (G:\Projects\ArkaSoftwares\React\arka-react\node_modules\next\dist\build\index.js:82:25)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment