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.