How to remove client-side JavaScript from Gatsby

I build my reviews site using Gatsby. It's cool, the graphql is powerful but there's something that was buggering me.

Why does my static site need client-side JavaScript?

There's no reason for it. Gatsby will argue about faster route changing this way, sure. For a high-traffic site where users visit multiple pages on the same visit, it makes sense. Not my case.

Astonishingly Gatsby does not provide an out-of-the-box setting or flag to amend this issue. I would've loved for a boolean in the config static if I need JS. To it builds a a truly static site, which can be done with Jekyll, Hugo or Eleventy.

No JavaScript plugin

I found this plugin but it doesn't seem to work with sites under a subfolder such as example.com/mysite

Then I encountered this Gist with the small amendment needed so it works in a subdirectory installation. It's based on the plugin but you don't need the plugin installed to work.

Create a gatsby-ssr.js file in the root directory of your site. it will remove all JavaScript from the final build (not when running the develop task).

โš ๏ธ It did not fully work in my Gatsby 2.25.3, it needed a small optional chaining around the polyfill condition.

Here's my updated version:

let pageScripts /* * The "scripts" variable is not documented by Gatsby, https://www.gatsbyjs.org/docs/ssr-apis/#onRenderBody, and that is probably for a good * reason. The variable contains the scripts the Gatsby internals, https://github.com/gatsbyjs/gatsby/blob/d9cf5a21403c474846ebdf7a0508902b9b8a2ea9/packages/gatsby/cache-dir/static-entry.js#L270-L283, * puts into the head and post body. We will be relying on this undocumented variable until it does not work anymore as the alternative is * to read the webpack.stats.json file and parse it ourselves. */ export function onRenderBody({ scripts }) { if (process.env.NODE_ENV !== "production") { // During a gatsby development build (gatsby develop) we do nothing. return } // TODO maybe we should not even wait and see if Gatsby removes this internal "script" variable and code around the issue if the variable is not there. if (!scripts) { throw new Error( "gatsby-plugin-no-javascript: Gatsby removed an internal detail that this plugin relied upon, please submit this issue to https://www.github.com/itmayziii/gatsby-plugin-no-javascript." ) } pageScripts = scripts } // Here we rely on the fact that onPreRenderHTML is called after onRenderBody so we have access to the scripts Gatsby inserted into the HTML. export function onPreRenderHTML( { getHeadComponents, pathname, replaceHeadComponents, getPostBodyComponents, replacePostBodyComponents, }, pluginOptions ) { if ( process.env.NODE_ENV !== "production" || checkPathExclusion(pathname, pluginOptions) ) { // During a gatsby development build (gatsby develop) we do nothing. return } replaceHeadComponents( getHeadComponentsNoJS(getHeadComponents(), pluginOptions) ) replacePostBodyComponents( getPostBodyComponentsNoJS(getPostBodyComponents(), pluginOptions) ) } function getHeadComponentsNoJS(headComponents, pluginOptions) { return headComponents.filter(headComponent => { // Not a react component and therefore not a <script>. if (!isReactElement(headComponent)) { return true } if ( pluginOptions.excludeFiles && headComponent.props.href && RegExp(pluginOptions.excludeFiles).test(headComponent.props.href) ) { return true } // Gatsby puts JSON files in the head that should also be removed if javascript is removed, all these Gatsby files are in the // "/static" or /page-data directories. if ( headComponent.props.href && (headComponent.props.href.startsWith(`${__PATH_PREFIX__}/static/`) || headComponent.props.href.startsWith(`${__PATH_PREFIX__}/page-data/`)) ) { return false } return ( pageScripts.find(script => { return ( headComponent.props.as === "script" && `${__PATH_PREFIX__}/${script.name}` === headComponent.props.href && script.rel === headComponent.props.rel ) }) === undefined ) }) } function getPostBodyComponentsNoJS(postBodyComponents, pluginOptions) { return postBodyComponents.filter(postBodyComponent => { // Not a react component and therefore not a <script>. if (!isReactElement(postBodyComponent)) { return true } if ( pluginOptions.excludeFiles && postBodyComponent.props.src && RegExp(pluginOptions.excludeFiles).test(postBodyComponent.props.src) ) { return true } // These are special Gatsby files we are calling out specifically. if ( postBodyComponent.props.id && (postBodyComponent.props.id === "gatsby-script-loader" || postBodyComponent.props.id === "gatsby-chunk-mapping") ) { return false } if (postBodyComponent.props.src?.indexOf("polyfill") > -1) { return false } return ( pageScripts.find(script => { return ( postBodyComponent.type === "script" && `${__PATH_PREFIX__}/${script.name}` === postBodyComponent.props.src ) }) === undefined ) }) } function isReactElement(reactNode) { return reactNode.props !== undefined } function checkPathExclusion(pathname, pluginOptions) { if (!pluginOptions.excludePaths) return false return RegExp(pluginOptions.excludePaths).test(pathname) }
Code language: JavaScript (javascript)

Performance

  • Reduced from about 35 requests to less than 10.
  • The site finishes loading in about 0.7 seconds comparted to 1.5 seconds when using the JavaScript (and JSON) files.

Leave a Reply

Add <code> Some Code </code> by using this tags.

*
*