Improving performance with guess.js

July 30, 2020

By accessing Google Analytics data, gatsby-plugin-guess-js is a plugin that can determine which pages a user is most likely to visit next and preload them selectively.This leads to fewer requests improving performance on low bandwidth networks.

Here’s a blog post with instructions on installing Google Analytics on your website first.

Google Analytics works only on a production build. The guess-js plugin downloads a file from Google Analytics in a production build and uses the Analytics data as predictive analysis for page navigation on the website.

After installing Google Analytics do the following:

  1. Install the gatsby-plugin-guess-js with the following command:

    npm install --save gatsby-plugin-guess-js
  2. Add the following to gatsby-config.js as per instructions on guess-js:

    {
    resolve: "gatsby-plugin-guess-js",
    options: {
    // Find the view id in the GA admin in a section labeled "views"
    GAViewID: process.env.GATSBY_VIEW_ID,
    // Add a JWT to get data from GA
    jwt: {
    client_email: process.env.GATSBY_GOOGLE_SERVICE_ACCOUNT_EMAIL,
    private_key: process.env.GATSBY_GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY,
    },
    minimumThreshold: 0.03,
    // The "period" for fetching analytic data.
    period: {
    startDate: new Date("2020-06-1"),
    endDate: new Date(),
    },
    },
    },
  3. Instead of checking in keys into your git repo, use environment variables as instructed in the post on Google Analytics.

    You do this by creating the files .env.development and .env.production and putting these key=value pairs in it. Instructions on how to get the values are in #10 and #11.

    File .env.production:

    GATSBY_VIEW_ID="1234"
    GATSBY_GOOGLE_SERVICE_ACCOUNT_EMAIL="emailaddress@xyz.iam.gserviceaccount.com"
    GATSBY_GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMTx
    EdC\netc\nEw3qw==\n-----END PRIVATE KEY-----\n"

    (GATSBY_GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY is on one line, not two as shown above) Note that the GATSBY_GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY above has \n which works on your local machine. Later on we will see how this causes problems on Netlify, the CDN I am using.

    Along with that, add the following lines to the top of gatsby-config.js:

    const activeEnv =
    process.env.GATSBY_ACTIVE_ENV || process.env.NODE_ENV || "development"
    console.log(`Using environment config: '${activeEnv}'`)
    require("dotenv").config({
    path: `.env.${activeEnv}`
    })
  4. To integrate this plugin with CI system, you need to get a JSON Web Token(JWT) for your Google Analytics account, so that no human intervention is needed for authentication.

  5. Go to Google Developers Console, select the project dropdown, and it opens up a panel in which you can create a new project.

  6. View the project, and you will see a Project name, Project ID and Project number on the left.

  7. In the middle panel under APIs, go to API’s overview and click on Credentials. Alternatively, go to APIs and Services, go to credentials.

  8. Click on Create Credentials. Click on Service Account. Create service account and click on Continue.

  9. Scroll down and you will see a Service Account with an email ending in iam.gserviceaccount.com. Click on the edit icon, and you will see a Keys section. Add a key. Create a new key with key type JSON. Save the private key on your computer.

  10. Open this file with the private key, it has a key-value pair for client_email and private_key. Add those to the jwt section of gatsby-plugin-guess-js in gatsby-config.js. You could also create environment variables for client_email and private_key.

  11. Go to Google Analytics Admin panel.It has 3 panels: Account, Property and View. Under Property for this website, go to Property User Management. Under there create a new user or use your existing user. In the panel View, go to View Settings, which has a View ID. Copy this and add it to gatsby-config.js as the GAViewID.

  12. Based on the instructions on gatsby-plugin-guess-js it is not clear if this JWT token is valid only for a finite time.

  13. On gatsby build you might see an error indicating that the Analytics Reporting API is disabled for the project. The error message also gives you the link to enable it.

  14. You may get an error that says that the user does not have a google analytics account. This stack overflow thread has a solution. Go to Google Analytics->Admin. Under the View panel, go to View User Management and add in the JWT client_email value as a user with Read & Analyze permissions.

  15. If you used environment variables above, be sure to add them to your CDN(Netlify) environment.

  16. I got the following build error on Netlify:

    11:10:09 PM: success write out requires - 0.041s
    11:10:55 PM: error Error: error:0909006C:PEM routines:get_name:no start line
    11:10:55 PM: at Sign.sign (internal/crypto/sig.js:105:29)
    ...
    ...
    11:10:55 PM: at /opt/build/repo/node_modules/guess-ga/dist/guess-ga/index.js:1:6794 {
    11:10:55 PM: library: 'PEM routines',
    11:10:55 PM: function: 'get_name',
    11:10:55 PM: reason: 'no start line',
    11:10:55 PM: code: 'ERR_OSSL_PEM_NO_START_LINE'
    11:10:55 PM: }
    11:10:55 PM: error UNHANDLED REJECTION error:0909006C:PEM routines:get_name:no start line
    11:10:55 PM:
    11:10:55 PM:
    11:10:55 PM: Error: error:0909006C:PEM routines:get_name:no start line
  17. We have 3 environment variables that have been added to Netlify:

    • GATSBY_GOOGLE_SERVICE_ACCOUNT_EMAIL
    • GATSBY_GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY
    • GATSBY_VIEW_ID

    GATSBY_GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY has newlines in it, \n, which are causing the error Error: error:0909006C:PEM routines:get_name:no start line.

  18. To fix this I got a tip from this thread on Netlify.

  19. Here’s the synopsis of the fix:

    • In Netlify create an env var GATSBY_GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY with _ instead of \n
    • In netlify-setup.sh output the value of this env var to a file .env.private_key
    • In package.json, run the netlify-setup.sh script on preinstall
    • In gatsby-config.sh, read the key from .env.private_key and replace _ with \n in memory. Now the code will work.
    • A few tweaks to make sure it works even if .env.private_key is not available in the local environment. In that case it picks up the values .env.production(which is NOT checked in)
  20. First, replace all \n with an underscore _ in GATSBY_GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY environment variable in Netlify. From this:

    GATSBY_GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY = -----BEGIN PRIVATE KEY-----\nLONG\nKEY\nWITH\nNEWLINES\n-----END PRIVATE KEY-----\n

    to:

    GATSBY_GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY = -----BEGIN PRIVATE KEY-----_LONG_KEY_WITH_NEWLINES_-----END PRIVATE KEY-----_

    (No quotes around the actual key)

  21. Then create a netlify-setup.sh script that puts this key from the Netlify environment into a file. Be sure NOT to console.log the private key.

    netlify-setup.sh:

    #!/usr/bin/env bash
    # Check if we're running in a Netlify environment
    # See https://www.netlify.com/docs/continuous-deployment/#environment-variables
    # This line is for testing
    # export DEPLOY_PRIME_URL="yes"
    if [ ! -z "${DEPLOY_PRIME_URL}" ]; then
    echo "Running netlify-setup.sh"
    # $GATSBY_GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY environment var has \n replaced with _
    #Do NOT echo $GATSBY_GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY in checked-in script.
    # Put the value of GATSBY_GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY with underscores
    # into a file
    echo -e "${GATSBY_GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY}" > .env.private_key
    chmod og-rwx .env.private_key
    fi;
  22. In package.json, under the scripts section add:

    "scripts": {
    "preinstall": "bash netlify-setup.sh",
    "dev": "gatsby develop",
    ...
    }
  23. In gatsby-config.js add the following code on top:

    const activeEnv =
    process.env.GATSBY_ACTIVE_ENV || process.env.NODE_ENV || "development"
    console.log(`Using environment config: '${activeEnv}'`)
    require("dotenv").config({
    path: `.env.${activeEnv}`
    })
    fs = require('fs')
    var googlePrivateKey = null;
    try{
    const syncDataBuffer = fs.readFileSync('.env.private_key', 'utf8');
    if (syncDataBuffer){
    console.log(`syncDataBuffer: ${syncDataBuffer}`);
    googlePrivateKey= syncDataBuffer.replace(/_/g, '\n');
    console.log(`New Key with \\n: ${googlePrivateKey}`);
    }
    }
    catch(e)
    {
    console.log(`Warning reading file .env.private_key, using env var
    GATSBY_GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY: ${e}`)
    }
    if (!googlePrivateKey)
    googlePrivateKey = process.env.GATSBY_GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY;
  24. In gatsby-config.js replace the jwt private_key with this new key with \n:

    gatsby-config.js:

    {
    resolve: "gatsby-plugin-guess-js",
    options: {
    // Find the view id in the GA admin in a section labeled "views"
    GAViewID: process.env.GATSBY_VIEW_ID,
    // Add a JWT to get data from GA
    jwt: {
    client_email: process.env.GATSBY_GOOGLE_SERVICE_ACCOUNT_EMAIL,
    private_key: `${googlePrivateKey}`,
    },
    ...
    }
  25. Alert: Be sure to not checkin any code where you log the key to the output

  26. Lastly how do you test that guess.js is actively doing the prefetching of links? I’m not sure…


tufan.io
Making it simple to create, manage & secure cloud applications.