Shubho.dev logo
Gatsby

Add a no-js class in Gatsby

I have been working with GatsbyJS for the past week. I am trying to set up my new blog. My old blog used to be on WordPress. But after working with Hugo for my recipe blog, I fell in love with static site generators. I learnt HTML/CSS using static HTML sites. My first website contained folders of HTML pages. I loved that approach. However, I outgrew it when I started journaling. Copying the HTML template every time and then replace the latest writing was a pain. When I wanted a design refresh (which was often), it was painful. And that is how I discovered WordPress.

My initial foray with Static Site Generators (SSG) was Hugo. It had a steep learning curve for me, especially since I didn’t know template syntax in Golang. But I credit Hugo for spiking my interest in Golang, and I studied the language due to it (though you do not need to know Go to learn using Hugo).

However, the language I am most comfortable in is JavaScript. I was late to the React game (Angular is my first love). I started using it two months back. Once I got comfortable enough to code in React, I wanted to create my blog in Gatsby - static site, JavaScript, and React. I love it.

Problem Statement

Add a “no-js” class to the document head in a Gatsby site.

Why do it?

I have this terrible habit of checking websites by disabling JavaScript. Not because I want to find issues with the site but mostly to find the fresh ways people make their site. Some sites create wonderful fallbacks for their site, and it’s great to see the way they enhance the experience with JavaScript. And others make fantastic and stunning websites without JavaScript. With my Gatsby site, I wanted to see how my site will behave by disabling JavaScript.

Note - Do not disable JavaScript from your Developer Tools. That will not be an actual test of the site behavior as the service workers are still loaded. To altogether disable JavaScript, do it from the browser settings.

So I disable JavaScript and fire up my Gatsby site gatsby build; gatsby serve. Everything looks good. It does display that “This app works best with JavaScript” that Gatsby automatically adds. But other than that, all is well. However, some JavaScript-only functionality will not work. e.g., I had a theme switcher. It works with CSS Variables, but the actual switching and remembering of the old settings are JavaScript. So the switcher is there, but the user clicks on it, and nothing happens. Wouldn’t it be nice if all JavaScript functionality doesn’t even show up? Many sites handle this by adding a no-js class to the <head> element.

Issues galore

The no-js class is added to the HTML by default. JavaScript is used to replace it. The JS code is the following three liner.

js
window.addEventListener('DOMContentLoaded', function() {
    document.documentElement.className = 
       document.documentElement.className.replace("no-js","js");
});
  1. I am using the react-helmet library to manage the header. Using the htmlAttributes property of <Helmet> I am already managing the class property. So I added the no-js class there. And I put the above JS code in a separate JS file, and I call it in gatsby-browser.js file. Does it work? No. Even though my JS file is sourced, react-helmet is dynamic. It fires and keeps the header updated even when I am replacing it from my static HTML. And worse, the no-js class remains every time so all my JavaScript-only behavior is gone even when it is enabled. So I cannot club no-js class to react-helmet as I am already managing it there.
  2. I put the no-js as a body class. But, you guessed it, I am managing the body classes too using react-helmet. So that’s a no go.
  3. As mentioned here [Customize Gatsby HTML] I copy over the html.js. I inlined the script in the <head> tag and manually add the class to the <head>. Doesn’t work as react-helmet removes the class. 😢

The Solution

So finally I figured it out. Since I want to continue using react-helmet to manage my head classes, I needed some other property to hold this value. So I add a data-html-class="no-js" to the <head> in html.js file. And then I inline the script, but instead of replacing class, I do a setAttribute. Here is how the template looks (only showing the HTML template) from html.js. The relevant parts are highlighted.

jsx
export default function HTML(props) {
    <html data-body-class="no-js" {...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"
        />
        {props.headComponents}
      </head>
      <body {...props.bodyAttributes}>
        {props.preBodyComponents}
        <div
          key={`body`}
          id="___gatsby"
          dangerouslySetInnerHTML={{ __html: props.body }}
        />
        {props.postBodyComponents}
        <script dangerouslySetInnerHTML={{            __html: `            window.addEventListener('DOMContentLoaded', function() {                document.querySelector('html').setAttribute('data-body-class', 'js')            });            `        }}/>      </body>
    </html>
  )
}

Now in CSS, I can have this.

css
html[data-body-class='no-js'] .only-js {
    display: none;
}

Whichever elements are JavaScript-only, I add the class only-js to them, and their display property is set to none when JavaScript is disabled.

Conclusion

HTML is awesome. JavaScript is awesome. Gatsby is awesome. I am in love.