Does CloudFlare’s Rocket Loader Help with Core Web Vitals? Maybe.

Like many of you I’m sure, one of my sites took a little hit from Google’s June Core Web Vitals search ranking update, and I’ve been spending a lot of time digging into these number in an attempt to find a shortcut or two that might help get my metrics up and recover a few search positions.

Checking Your Core Web Vitals

A great tool for measuring Core Web Vitals statistics is the Google PageSpeed Insights test page located here…

https://developers.google.com/speed/pagespeed/insights/

…which allows you to submit a URL and then let Google tell you what it thinks of the page. There are another of other tools around that use the LightHouse APIs and try to measure these metrics, but I’m assuming this is likely the closest approximation to what Google uses for actual ranking results.

Weird Things to Know About the PageSpeed Insights Checker

There are a few things to note about using this particular tool.

First, take note that the page reports different data for mobile and desktop analysis. The mobile analysis is probably what you care about since Google announced mobile first ranking, even for desktop search results. For the “Lab Data” section of the mobile results, the measurements look truly dreadful in most cases, but if you read the fine print Google is performing the mobile analysis as though you’re on an ancient Android phone accessing the internet from a 3G network in central Timbuktu. So for practical purposes, these measurements should be treated as relative measurements and not absolute reflections of what a user on a more up-to-date device on WiFi or a modern cellular network.

Second, it reports a lot of results that don’t really have anything to do with the test you run immediately ran. In particularly, the top part of the results page shows “Field Data” which is telemetry collected from real-world Chrome users. You won’t see these values change from test to test because it’s on-going data collection from a 28 day window of live visitors. If you’re tuning your site, you won’t see these numbers change for several days at a minimum, and even then only if you’ve made a substantial tweak.

Finally, the “Lab Data” section, which is the actual data from the test you just ran, will vary by quite a bit from run to run. If you change something and want to really measure the difference, you need to run this test multiple times and analyze the aggregate results.

JavaScript weighs in on many of the dimensions of the Core Web Vitals statistics, and in looking into the detailed breakdown of the diagnostics, I noticed the CloudFlare’s Rocket Loader script was one of the heavyweights that PageSpeed Insights said was contributing negatively to multiple statistics.

What is CloudFlare’s Rocket Loader?

If you’ve been around the site here for long, you know I’m a big fan of CloudFlare, so much so that I actually starting buying some of their stock (and for full disclosure, other than as a very small share holder, I have no financial relationship with them.)

Their Rocket Loader optimization is available as part of their free tier and attempts to optimize the way JavaScript files are delivered to site visitors. For detailed description of what Rocket Loader does, this link from CloudFlare’s blog probably describes it best…

https://blog.cloudflare.com/too-old-to-rocket-load-too-young-to-die/

The TL/DR version is that when CloudFlare caches your site content, it figures out which JavaScripts are on the page and then bundles them up into larger requests so that they can be more efficiently cached and delivered to the browser. This optimizes a lot of issues related to asynchronous loading, scripts that interact with the DOM and more, but in general Rocket Loader is supposed to help with things like Core Web Vitals.

Rocket Loader versus Core Web Vitals Deathmatch!

To put Rocket Loader to the test, I ran multiple tests against the multiplication worksheets page on DadsWorksheets.com, which is one of the higher volume pages on the site and one where I’ve been doing most of my Core Web Vitals optimization work.

I ran the test ten times each with Rocket Loader enabled and just with regular JavaScript, threw away the best and worst score from each group and then averaged the results. The data looks like this…

To make that just a little easier to compare, here’s just the average rows…

Counter intuitively, the PageSpeed Insights score with Rocket Loader enabled seemed to be close to 3 points worse than running regular JavaScripts on the site. But as usual with Google, the story is a little more complicated.

An interesting point in Rocket Loader’s favor comes from taking note of the ranges returned in the test numbers… Rocket Loader seemed to substantially reduce the variability in the metrics compared to regular JavaScript. This definitely tells me Rocket Loader is doing something (arguably something positive) in terms of the way the site behaves.

What seems to throw the score metric off is primarily First Content Paint (FCP) and Largest Contentful Paint (LCP). Both of these numbers are meaningfully higher. And given that version 8 of the Lighthouse score metric assigns a whopping 25% weighting to LCP, it’s pretty obvious that’s the culprit. But why?

What the Heck is Going On?

I’m still guessing at what this data is telling me, but I have a pretty good suspicion about what’s going on. The site presently is statically generated by Nuxt.js, which loads a ton of small (and not so small… manifest.js? Seriously guys?) JavaScript files on each page.

I suspect Rocket Loader is bundling up and loading all of these files in one big gulp, which is great from the consistency point of view, but apparently not so great in that this load and rehydrating is getting in front of the render pipeline with the largest paint element in it.

What that means is my case here may be slightly pathologically and atypical, but it’s definitely one of those reasons why you don’t just blindly assume flipping a switch on a dashboard is a shortcut to great performance.

So What Am I Doing Next?

My hope is I can untangle those and make sure the largest (and first) contentful paint are handled before the JavaScript needs to execute, in which case both the FCP and LCP numbers should come down, leaving the small SI and TBT advantages Rocket Loader contributed.

LCP continues to be a struggle for me for some reason, not the least because the static build process with Nuxt is unbelievably slow on a site with over 10,000 pages. I have some hopes Nuxt 3 will be better, but I’ve got on my to-do list to look at Vite and vite-ssg+vite-plugin-md+vite-plugin-pages in the near future, but in the short term I’m siloed. If you have experience with that Vite stack, let me know if the comments below how it went.

I think the end game is to leave Rocket Loader enabled and struggle through optimizing my LCP issues away. I’ll follow up with another post if I make any interesting progress, but if I don’t, the numbers don’t lie… And I may wind up switching Rocket Loader off until I’m on a less complicated static deployment.

 

 

 

Adding Snap.svg to Vue.js Projects Created with vue-cli 3.0

My front end tool chain has finally settled into something I’m getting comfortable with. But these tools don’t stand still, so the learning continues.

One of the higher traffic posts on this blog has been my discussion of how I integrated Snap.svg into my Vue.js and Nuxt.js projects. I wrote that article because while it wasn’t fun, it helped me learn a lot about Webpack configuration.

That article was written at a point in time when where I was maintaining standalone Webpack configuration files for my projects. A big part of the trouble with this approach is that it intrinsically couples a large number of external dependencies together. My Vue.js projects rely on a number of third party libraries, Webpack uses a collection of loaders and the build tool chain itself has a number of dependencies like ESLint and Babel that bring along their own school of remora fish.

So, you can understand why once you’ve got a fairly complicated project and build process configured in Webpack, that the last thing you typically want to do is touch it. It’s even worse when you introduce dependencies like Snap.svg that, because of their lack of module support, require mystical Webpack incantations to get them to work.

I just had to extract this slope intercept calculator from a project that was generated without the CLI, so I was using the older Webpack mysticism to get Snap.svg working. With the need to create this in a new project, startig with the latest Vue.js and the vue-cli was the natural approach… But that meant getting our friend Snap.svg working again. Read on to find out how…

How The vue-cli reduces webpack Configuration hell

Let’s be honest, not many people like working on Webpack configurations.

One of the things the vue-cli sets out to do is to hide away some of the complexity associated with Webpack configurations. This makes building compiled Vue.js projects a bit less intimidating, but it also allows the whole tool chain to be upgraded without breaking what would have been a hand-coded Webpack configuration.

There’s still a Webpack configuration hiding under the hood of course, it’s just that the CLI manages it. And if you upgrade Vue.js and it’s tool chain, in theory it becomes a seamless process because the configuration is “owned” by the CLI and it’s not a visible part of your project that you’ve been tempted to monkey around with internally.

This management of the Webpack configuration by vue-cli is in contrast to many other tools that “eject” a configuration when the project is first setup, or when you might need to make an unsupported modification. At that point, the Webpack configuration file is owned by the developer (lucky you!) and a future upgrade becomes something to potentially be feared.

Fortunately, vue-cli 3.0 introduced a new way to make modifications to the generated Webpack configuration without having to eject a configuration and be responsible for maintaining it forever forward.

Webpack chain: how to modify a vue-cli webpack configuration without ejecting

The magic comes from webpack-chain, a package that came out of the Neutrino.js project. It’s bundled into the vue-cli and the implementation details are a slightly different, but the documentation at the link above is useful for digging into individual options. There’s some examples specific to vue-cli at this link although they fell short of the complete reference at the actual webpack-chain documentation.

Webpack-chain allows you to create little configuration patches that get applied programmatically to the Webpack configuration that is built by a vue-cli project each time you compile. The advantage of this is that you only need to specify the eccentric bits of your project’s build configuration, and if you subsequently upgrade to Webpack 7 or Vue.js 5 all of the stock configuration provided by the rest of the toolchain should hopefully get upgraded appropriately.

An example of this is getting CSS preprocessors like Sass or Less running. Normally this would take some Webpack gymnastics, but the vue-cli does this for you when you configure a project and you never have to touch the actual Webpack configuration. As vue-cli gets updated to use later versions of Webpack or whatever CSS preprocessor you’re using, these upgrades will come along for free in your project without having to go back and touch the Webpack configuration. Woohoo!

Where you do need to go outside the box, you can do it by setting up a “chain” in separate configuration file. You may still have update your custom configuration bits in your chain on undertaking a major version upgrade, but those changes are scoped and isolated, and they’re definitely more succinct. I much prefer this compared to picking through 1000 lines of Webpack boilerplate and finding where I’ve done something outside the box.

The slightly inconvenient part of this is that the syntax of webpack-chain is slightly different from the JSON object structure you may be more familiar with if you’re a Webpack old timer.

As an example, let’s look at our friend Snap.svg.

Getting Snap.svg Working with VUE-CLI

As in manual configurations, we’ll use the imports-loader to load Snap.svg in our project. So once we’ve created a new project with vue-cli, we’ll need to install Snap.svg and imports-loader as appropriate dependencies…

npm install --save snapsvg
npm install --savedev imports-loader

We don’t need to explicitly install webpack-chain as part of our project as vue-cli already bundles this into itself.

To create a chain that gets applied to the vue-cli generated Webpack configuration, you need to create a file named vue.config.js in your project’s root folder. Here’s the entire file I’m using to pull Snap.svg into my project…

module.exports = {
  chainWebpack: config => {
    config.module
      .rule("Snap")
        .test(require.resolve("snapsvg/dist/snap.svg.js"))
        .use("imports-loader")
        .loader("imports-loader?this=>window,fix=>module.exports=0");

    config.resolve.alias.set("snapsvg", "snapsvg/dist/snap.svg.js");
  }
};

That will handle getting Snap.svg resolved and imported when we need to. Again, the webpack-chain documentation illuminate how this is mapped back to traditional JSON Webpack configuration structures, but you can see readily how you might setup other behaviors like plugins or other loaders.

All that’s left is to reference it in the source to one of our components, something in very broad strokes like this…

<template>
  <div >
    <svg id="mySVG"  />
  </div>
</template>
<script>

import Snap from "snapsvg";  // <-- triggers our import resolution

export default {

  data() {
    return { mySVG: undefined };
  },

  mounted() { 
    this.mySVG = window.Snap("#mySVG");
  },

  methods: { 
     // draw amazing stuff here with this.mySVG
  }
}

</script>

Troubleshooting webpack-chain

The vue-cli includes commands to dump a copy of the Webpack configuration with your webpack-chain modifications applied to it.

vue inspect > ejected.js

You can open this file to look and see how the chain was applied to generate corresponding Webpack rules. In our case, this is what we got for the vue.config.js file described above…

  resolve: {
    alias: {
      '@': '/Volumes/Hactar/Users/jscheller/vue/myproject/src',
      vue$: 'vue/dist/vue.runtime.esm.js',
      snapsvg: 'snapsvg/dist/snap.svg.js'
    },
   
    [ ... and about 1000 lines later... ]
 
      /* config.module.rule('Snap') */
      {
        test: '/Volumes/Hactar/Users/jscheller/vue/myproject/node_modules/snapsvg/dist/snap.svg.js',
        use: [
          /* config.module.rule('Snap').use('imports-loader') */
          {
            loader: 'imports-loader?this=>window,fix=>module.exports=0'
          }
        ]
      }

A great thing about running the ‘vue inspect’ command is that it will give you console output if your chain rules have a problem. If you build up a webpack-chain in your vue.config.js file that doesn’t seem to be working right, this is probably one of the first things you should check to see if it’s actually getting applied correctly.

Adding Snap.svg to Vue.js and Nuxt.js Projects

This post may be out of date for what you need… There’s an updated article that deals with adding Snap.svg to projects created with the vue-cli 3.0 and more recent 2.x versions of Vue.js. Click here to read it!

SVG is amazing, and if you’re building any custom vector graphics from your client code, one of the easiest libraries to use is Snap.svg. I’ve used it in a number of projects, including vanilla JavaScript and various transpiling setups including Transcrypt.

I’m trying to go a little more mainstream after wasted years of time on fringe technologies that fell out of favor.

I’m spending a lot of time these days learning Vue.js and really hoping this is going to be a worthwhile long term investment in my skillset. So it was only a matter of time before I found myself needing to get Snap.svg working in my Vue.js projects, which meant some extra fiddling with WebPack.

Getting Snap.svg Working with Vue.js

Out of the gate, there’s some hurdles because Snap mounts itself on the browser’s window object, so if you’re trying to load Snap through WebPack (as opposed to just including it in a project using a conventional script tag), you need to do some gymnastics to get WebPack’s JavaScript loader to feed the window object into Snap’s initialization logic. You can find an overview of the problem in this GitHub issue which illustrates the obstacles in the context of using React, but the issues as they relate to Vue.js are the same.

I’m assuming you have a Vue.js webpack project that you started with vue-cli or from a template that has everything basically running okay, so you’ve already got Node and webpack and all your other infrastructure in place.

For starters, you’ll want to install Snap.svg and add it to your project dependencies, so from a terminal window open and sitting in the directory where your project’s package.json/package-lock.json sit…

npm install --save snapsvg

That will download and install a copy of the Snap.svg source into your node_modules directory and you’ll have it available for WebPack to grab.

Normally you’d be able to use a package installed like this by using an import statement somewhere, and you’d think you could do this in your Vue project’s main.js file, if you start down this path you’ll get the window undefined issue described in that GitHub link above.

The tricky bit though is getting WebPack to load the Snap properly, and to do that we’ll need a WebPack plugin that lets us load as a JavaScript dependency and pass some bindings to it. So, in that same directory install the WebPack imports-loader plugin…

npm install --savedev imports-loader

To tell the imports-loader when it needs to do its magic, we have to add it to the WebPack configuration. I changed my webpack.base.conf.js file to include the following inside the array of rules inside the module object…

 module: {
   rules: [
      ...
       {
       test: require.resolve('snapsvg'),
       use: 'imports-loader?this=>window,fix=>module.exports=0',
       },
      ...
     ]
   },

Now we can load Snap.svg in our JavaScript, but imports-loader uses the node require syntax to load the file. So in our main.js, we can attach Snap.svg by telling WebPack to invoke the exports loader like this…

const snap = require(`imports-loader?this=>window,fix=>module.exports=0!snapsvg/dist/snap.svg.js`);

…and then attach it to our root Vue instance, still in main.js, something like this…

const vueInstance = new Vue( {
 el: '#app',
 snap,
 router,
 axios,
 store,
 template: '<App/>',
 components: { App }
} );

export { vueInstance };

There is some redundancy in that require() call and the way we setup the module resolution in the WebPack configuration. I’m fuzzy about why I seemed to need this in both spots, but it works so I’m running with it. If you have insights they’d be appreciated; let me know in the comments.

Getting Snap.svg Working with nuxt.js

Nuxt requires a slightly different twist, because as you’re aware a typical Nuxt project doesn’t have either a main.js file or a native copy of the WebPack configuration. We need to make the same changes, but just in a slightly different spots.

You need to install both snapsvg and imports-loader just like we did above…

npm install --save snapsvg
npm install --savedev imports-loader

The way we modify the WebPack configuration in a Nuxt project is to create a function that accepts and extends the WebPack configuration from with your nuxt.config.js file…

/*
 ** Build configuration
 */

 build: {
   extend(config, ctx) {
      config.module.rules.push( {
        test: require.resolve('snapsvg'),
        use: 'imports-loader?this=>window,fix=>module.exports=0',
       } );
     }
   }

Since we don’t have a main.js, we need to use a Vue.js plugin to inject shared objects and code into Vue. In your projects plugins folder, create a file named snap.js that contains code to attach a snap object created again using imports-loader…

export default ({ app }, inject) => {
  app.snap = require(`imports-loader?this=>window,fix=>module.exports=0!snapsvg/dist/snap.svg.js`);
}

…and back in your nuxt.config.js file, include this plugin…

plugins: [
   ...
   {src: '~/plugins/snap'},
   ...
],

These approaches seem to work well for me in both a standard Vue.js and Nuxt.js projects, but both of these setups have been cobbled together from reading a lot of other bits and pieces… If you’ve got a better or approach or see a way to clean up what I’ve done, please let me know.

Meanwhile, good luck with your Snap and Vue projects!