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

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,, and that is probably for a good * reason. The variable contains the scripts the Gatsby internals,, * 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" ) } 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 ( === "script" && `${__PATH_PREFIX__}/${}` === 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 ( && ( === "gatsby-script-loader" || === "gatsby-chunk-mapping") ) { return false } if (postBodyComponent.props.src?.indexOf("polyfill") > -1) { return false } return ( pageScripts.find(script => { return ( postBodyComponent.type === "script" && `${__PATH_PREFIX__}/${}` === 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)


  • 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

Your email address will not be published. Required fields are marked *