It’s been a challenge to performantly add scripts to websites since the dawn of time (well, since browser makers introduced the <script>
tag at least!). Script execution in the browser is blocking by default, and we as developers are responsible for implementing many useful scripts in our web pages without degrading the experience for the end-user.
As of Gatsby 4.15, Gatsby is releasing a built-in <Script>
component that aids in loading scripts performantly. The script component offers a convenient way to declare different loading strategies, and a default loading strategy that gives Gatsby users strong performance out of the box. Whether you want to leave the heavy lifting of managing scripts to Gatsby or you want maxiumum flexibility and control, the Gatsby <Script>
component is a great tool for the job.
Script Component is usable today, and we encourage you to try it out on your Gatsby site! See the reference documentation for the Gatsby Script API for full details. In this blog post, I want to walk through some of the decisions you will make when you use the Gatsby Script component. To do that, we’re going to build a small, interactive website that allows you to write in Markdown and see it rendered in HTML on the same page.
Before we dive in, feel free to browse or grab the source code from this blog post from the repo on GitHub. As an aside, I’ve also created a video that provides a quick demo overview of Script Component, which you can find embedded at the end of this post or view directly on YouTube.
- Companion repo: https://github.com/tyhopp/gatsby-script-blog-post-companion
- Deployed companion site: https://gatsbyscriptblogpostcompanionm.gtsb.io/
And now, onwards!
What are we building?
Let’s expand on what we’re building a bit more. You can check out the demo site deployed to Gatsby Cloud, or if you prefer to stay here’s a screenshot of what it looks like:
We’ll use the excellent marked module to do the markdown to HTML conversion for us. We could install it via npm, but we’d lose the ability to control loading strategies. In our case, we’re obsessed with performance, and we want maximum control to determine how our scripts are loaded.
Enter the Gatsby <Script>
component. We’ll create three separate implementations that each use a different loading strategy of the script component – post-hydrate
, idle
, and off-main-thread
. We’ll explore the nuances of each, and finally come to a conclusion on which is best for our use case.
Using the post-hydrate strategy
Alright, we have already created a new Gatsby site with npx create-gatsby
and we want to see the script component in action before we optimize. Here’s how we import the <Script>
component and load marked
in the most basic way:
Note – We’ll use TypeScript for this entire post. The script component can be used the same way in JavaScript.
In many cases, this is all you’ll need to use the Gatsby script component. Gatsby will load your script after the page has hydrated by default (the post-hydrate
strategy), so you get instant performance gains over the regular <script>
tag in the browser, even with async
or defer
applied.
Let’s create a component for the markdown-to-HTML functionality that we’ll also use when we try out the idle
and off-main-thread
strategies:
It works! Now if we open up the developer tools in your browser and inspect the runtime performance (in Chrome DevTools this is found in the performance tab), we’ll see something like this:
Circled in the top right is marked
, and it loads well after the framework script (hydration happens there) that loads right after the HTML document is requested.
This is good for us, because it means that the loading and execution of marked
does not compete with the framework bundle that makes our page interactive. We expect that our users are going to want to read and interact with other elements on the page first, and by the time they have, the lightweight marked
script will have loaded so the demo can be used!
Now, let’s try out the idle
strategy.
Using the idle strategy
Compared to post-hydrate
, the behavior of idle
is very much affected by what else is happening on the main thread in the page. Under the hood the idle
strategy makes use of the requestIdleCallback web API, which instructs the browser to only execute our script when the main thread is free.
In our case, we don’t have much else happening on the page for the time being, so we don’t expect to see much of a difference. It is easy to imagine down the line that our little demo might be used in a larger page, and at that point the choice between post-hydrate and idle will be something to reconsider given the specifics of the scenario.
Luckily, the Gatsby Script component makes switching between these two strategies a breeze. We only need to swap or apply a different value to the strategy attribute. In our case,
<Script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js" />
becomes:
<Script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js" strategy={ScriptStrategy.idle} />
Just for the sake of completeness, here’s what the performance tab shows when marked is loaded with idle:
Not much difference, both are equally performant! Now let’s explore the last strategy, off-main-thread
.
Using the off-main-thread strategy
Note – The off-main-thread
strategy is experimental.
The off-main-thread strategy is unique in that it leverages Partytown, a library that helps relocate resource intensive scripts into a web worker. It’s an interesting solution that may or may not work with various scripts due to certain trade-offs, but has the advantage of not asking the main thread to do any significant work after the initial service worker registration. That’s exciting!
The Partytown documentation is clear about the use case it’s best at handling:
Partytown is best suited for third-party scripts such as Google Tag Manager or Facebook Pixel, since they’re only handling user events and lazily posting data to their services in the background.
That’s not an exact match for our use case, and with this in mind we are likely better off using the post-hydrate
or idle
strategies.
Just for fun though, let’s see if we can get it to work. Using our <Demo>
component from before, we’ll create a new page and do something like this:
In addition in our gatsby-config
file, we’ll declare the marked URL as safe for Gatsby to proxy for Partytown:
If we fire it up we’ll see it works! It’s not optimal and relies on a polling approach that negatively impacts our site performance (Partytown will make many requests between the main thread and service worker), but we knew from the outset that off-main-thread
wasn’t the play. If we ever have to add Google Analytics or a similar script though, we’ll definitely revisit this.
Finally, a look at the performance waterfall:
As expected there is a small startup cost to request and set up the service worker, but once it is then we’re off to the races.
Wrapping up
Together we’ve walked through some of the decisions that you’ll encounter when using the Gatsby script component as a developer working on Gatsby sites. Put another way, we talked about the wonderful problem of having both power and flexibility at your fingertips with the Gatsby Script component, and how to go about analyzing its effect on your site’s performance.
I hope you enjoyed the ride, and I look forward to seeing what you’ll build with the Gatsby Script component!
Don’t forget to check out all the other exciting Gatsby product news from Launch Week, and follow our primary Gatsby Twitter account (@GatsbyJS) to stay in the loop with all the latest Gatsby news and updates.