260

I'm on a quest to reach 100/100 on PageSpeed and i'm almost there. I'm trying to find a good solution to cache Google Analytics.

Here is the message I get:

Leverage browser caching Setting an expiry date or a maximum age in the HTTP headers for static resources instructs the browser to load previously downloaded resources from local disk rather than over the network. Leverage browser caching for the following cacheable resources: http://www.google-analytics.com/analytics.js (2 hours)

The only solution i've found was from 2012 and I do not think it is a good solution. Essentially you copy the GA code and host it yourself. You then run a cron job to recheck Google once a day to grab the latest GA code and replace it.

http://diywpblog.com/leverage-browser-cache-optimize-google-analytics/

What else can I do to reach 100/100 while also using Google Analytics?

Thank you.

Gerhard
  • 6,850
  • 8
  • 51
  • 81
sjmartin
  • 3,912
  • 4
  • 19
  • 32
  • 1
    I used the cron method, Without cron usage ( loads and caches onload. i can share php code if you want ). And i got fixed my GA fixing suggestion. But little problem left there: I left "Cache-Control: max-age=604800" header. Which is much higher then 5 minutes cache. – Roman Losev Apr 03 '15 at 09:30
  • 6
    Is that really a good idea, though? Caching this file on your server means the browser will have to re-download it instead of re-using the one it has already cached by visiting other sites using Google Analytics. So it may actually *slightly* slow down your visitors. – s427 Jul 09 '15 at 09:46
  • Possible duplicate of [Leverage browser caching for 3rd party JS](https://stackoverflow.com/questions/38376871/leverage-browser-caching-for-3rd-party-js) – Joe Aug 22 '17 at 10:18

20 Answers20

259

Well, if Google is cheating on you, you can cheat Google back:

This is the user-agent for pageSpeed:

“Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.8 (KHTML, like Gecko; Google Page Speed Insights) Chrome/19.0.1084.36 Safari/536.8”

You can insert a conditional to avoid serving the analytics script to PageSpeed:

<?php if (!isset($_SERVER['HTTP_USER_AGENT']) || stripos($_SERVER['HTTP_USER_AGENT'], 'Speed Insights') === false): ?>
// your analytics code here
<?php endif; ?>

Obviously, it won't make any real improvement, but if your only concern is getting a 100/100 score this will do it.

Simon
  • 594
  • 2
  • 6
  • 25
NiloVelez
  • 3,611
  • 1
  • 22
  • 30
  • 4
    Clever......pity I use edge caching because this script will only work if requests reach your origin for every request :( – Amy Neville Aug 23 '16 at 08:31
  • 54
    Load it via JS then :) `if(navigator.userAgent.indexOf("Speed Insights") == -1) { /* analytics here */ }` – Rob W Oct 19 '16 at 14:09
  • Thank you, Half Crazed, that's a great approach for statically cached pages! – NiloVelez Oct 22 '16 at 11:28
  • 1
    @Jim See http://stackoverflow.com/questions/10734968/inject-external-javascript-file-in-html-body-after-page-load -- you would use this method inside of the `{ }` in my example, along with any other JS that GA uses (such as `ga('create', 'UA-XXXXX-Y', 'auto'); ga('send', 'pageview');` or whatever) – Rob W Dec 09 '16 at 16:34
  • 1
    @Jim I've added an answer which covers this. – Rob W Dec 09 '16 at 16:46
  • Any way to extend this to other tests? E.g. Google Page Speed and gtmetrix – Dan382 Jun 15 '17 at 19:00
  • 2
    With all due respect, that's cheating! Think that you may apply that rule for many other things, like improving speed by not loading content. Sooner or later, Google might start to penalise sites that use this rule, by comparing HTML from different user agents. – João Pimentel Ferreira Jun 17 '17 at 19:14
  • @JoãoPimentelFerreira Yup, that is named cloacking and it is already penalized by google. You can serve different content for google, but should keep your changes to secondary non-visible elements – NiloVelez Jun 23 '17 at 07:33
  • @Dan382 I've made some test with other services. It worked with services like pingdom who identify themshelves with a unique user agent. On the other hand, GTMetrix uses a computer farm with real browsers that keep their user-agent (chrome, firefox, edge), it would be more difficult to target that. – NiloVelez Jun 23 '17 at 07:40
  • What a hack! There's literally no point in doing this, just ignore the warning in PageSpeed Insights if you don't care. – Matti Virkkunen Jun 27 '17 at 17:42
  • @MattiVirkkunen the problem is that a lower insights score negatively affects your seo placement... even if that lower score is caused by "the google" :( – Rob W Aug 08 '17 at 15:04
  • 1
    @HalfCrazed Your time is probably better spent coming up with better content if your search ratings are low, instead of trying to play the system. – Matti Virkkunen Aug 11 '17 at 14:17
  • @MattiVirkkunen agreed. This "hack" isn't meant to improve SERP, only insights scores. – Rob W Aug 11 '17 at 17:29
  • How about if Pingdom (https://www.pingdom.com/) or GT metrics (https://gtmetrix.com) is used? – Andrien Pecson Nov 06 '17 at 08:26
  • Just a helpful heads up - Google cuts out the `https:` from it's new snippet of analytics code - Placing this in will make this trick work - Otherwise it will still show up unsolved. – LatentDenis Dec 20 '17 at 20:36
  • I think this has to be one of the most over rated answers I have seen on SO for a while now. PageSpeed Insights is a tool that lets you test your site for speed optimization so that when Google bot fetches it, the website will load faster. They don't say anywhere that SpeedInsight score is one of the indicators they use for ranking. – Uri Abramson Jan 10 '18 at 15:07
  • It's not used for ranking, but it's one of the factors that affect the quality score of Adwords campaigns. And yes, my answer is totally overrated :D – NiloVelez Jan 12 '18 at 09:32
  • 12
    Warning: This does not work anymore. Page Speed Insights powered by Lighthouse uses a default userAgent, that cannot be detected anymore. – David Vielhuber Jan 20 '19 at 14:50
  • 1
    @DavidVielhuber is there any other good solution for this problem then? – labago Mar 25 '21 at 21:44
  • Wow that was a really good one :) – Afsanefda May 16 '22 at 14:12
  • @Afsanefda, is it working for you? I have tried but not working for me. – user9437856 Sep 30 '22 at 04:47
  • @user9437856 I didn't try this but the whole idea was nice :) – Afsanefda Oct 04 '22 at 12:55
  • 1
    @Afsanefda, It's not working for me. – user9437856 Oct 05 '22 at 17:09
  • But be aware, CrUX metrics become not better with this hint! – Evgeniy Feb 23 '23 at 12:05
42

There's a subset of Google Analytics js library called ga-lite that you can cache however you want.

The library uses Google Analytics' public REST API to send the user tracking data to Google. You can read more from the blog post about ga-lite.

Disclaimer: I am the author of this library. I struggled with this specific problem and the best result I found was to implement this solution.

jehna1
  • 3,110
  • 1
  • 19
  • 29
23

Here is a really simple solution using JS, for basic GA tracking, which will also work for edge caches/proxies (this was converted from a comment):

if(navigator.userAgent.indexOf("Speed Insights") == -1) {
  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

  ga('create', 'UA-XXXXXXXXX-X', 'auto');
  ga('send', 'pageview');
}

Note: This is the default GA script. You may have other ga() calls, and if so, you would need to always check the user agent before calling ga(), otherwise it may error out.

Rob W
  • 9,134
  • 1
  • 30
  • 50
  • 2
    Reacting to the "Note:" section, You can declare `ga` as `ga = function(){};` before the snippet to fail silently when executed as `ga();` so you don't have to check the existence of this function everywhere in your code. – István Pálinkás May 16 '17 at 18:11
  • 2
    How to add this in script – Navnish Bhardwaj Jan 25 '18 at 06:28
  • 1
    Note: This likely no longer works. Speed Scores DO contribute as to search signals (see https://developers.google.com/search/docs/guides/page-experience) - to what extent is unknown. – Rob W May 18 '21 at 22:22
16

I wouldn't worry about it. Don't put it on your own server, it sounds like this is an issue with Google, but as good as it gets. Putting the file on your own server will create many new problems.

They probably need the file to get called every time rather than getting it from the client's cache, since that way you wouldn't count the visits.

If you have a problem to feel fine with that, run the Google insights URL on Google insights itself, have a laugh, relax and get on with your work.

Leo Muller
  • 1,421
  • 1
  • 12
  • 20
  • 68
    He wants to know how can he reach 100, not if 99 is ok. – Erick Engelhardt Nov 17 '15 at 12:05
  • 4
    This answer is not true, where the Analytics.js file is downloaded from does not impact whether or not analytics tracks. The issue of hosting your own analytics file is that you always have to manually update to the latest version (a few times a year). – Matthew Dolman Jun 09 '16 at 02:22
  • 1
    Thanks Matthew for pointing that out. Apparently I was wrong, which is good, but still I don't think it is a good idea to host this file on your own server because I can imagine it will create a lot of new problems. The OP question was how to get to 100 on pagespeed and my answer is not to worry about getting to that 100. That may be a really annoying answer, but that's me. – Leo Muller Jun 10 '16 at 08:17
  • 3
    good answer for people that got lost by thinking 99 is not good enough. better dedicate your time on real problems. – linqu Jan 11 '17 at 12:05
  • @ErickEngelhardt You are correct, but if people ask a question where you think they are not aiming for the best goal, you should give them a heads up which solution might serve them better. – observer Mar 20 '17 at 08:04
10

In the Google docs, they've identified a pagespeed filter that will load the script asynchronously:

ModPagespeedEnableFilters make_google_analytics_async

You can find the documentation here: https://developers.google.com/speed/pagespeed/module/filter-make-google-analytics-async

One thing to highlight is that the filter is considered high risk. From the docs:

The make_google_analytics_async filter is experimental and has not had extensive real-world testing. One case where a rewrite would cause errors is if the filter misses calls to Google Analytics methods that return values. If such methods are found, the rewrite is skipped. However, the disqualifying methods will be missed if they come before the load, are in attributes such as "onclick", or if they are in external resources. Those cases are expected to be rare.

Cameron Scott
  • 1,276
  • 2
  • 17
  • 37
7

You can try to host the analytics.js locally and update it's contents with a caching script or manually.

The js file is updated only few times a year and if you don't need any new tracking features update it manually.

https://developers.google.com/analytics/devguides/collection/analyticsjs/changelog

JJTalik
  • 86
  • 4
  • 2
    Be warned this is explicitly not supported by Google: https://support.google.com/analytics/answer/1032389?hl=en – steel Mar 23 '17 at 18:26
7

store localy analytics.js, but it is not recommended by google: https://support.google.com/analytics/answer/1032389?hl=en

it is not recommended cause google can update script when they want, so just do a script that download analytics javascript each week and you will not have trouble !

By the way this solution prevent adblock from blocking google analytics scripts

Froggiz
  • 683
  • 8
  • 13
  • It doesn't bypass Adblock completely (it still blocks ajax calls), but at least you get sessions and page views – NiloVelez May 19 '16 at 08:32
7

varvy.com (100/100 Google page speed insight) loads google analitycs code only if user make a scroll of the page:

var fired = false;

window.addEventListener("scroll", function(){
    if ((document.documentElement.scrollTop != 0 && fired === false) || (document.body.scrollTop != 0 && fired === false)) {

        (function(i,s,o,g,r,a,m{i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)})(window,document,'script','//www.google-analytics.com/analytics.js','ga');

        ga('create', 'UA-XXXXXXXX-X', 'auto');
        ga('send', 'pageview');

        fired = true;
    }
}, true);
j0k
  • 22,600
  • 28
  • 79
  • 90
ar099968
  • 6,963
  • 12
  • 64
  • 127
  • 9
    What if visitor does not scroll but just click a link. He will not be counted in analytics. – Ross Apr 11 '17 at 07:02
  • @RossIvantsiv you can handle also the click! – ar099968 Feb 20 '18 at 07:56
  • I would not recommend this solution. You can never asure all data being tracked, since some touch devices wont perform events initially. Take purchase tracking, where you return from third party window. No GTM or analytics tag loaded, the purchase is never tracked. Just my thoughts.. – Lervad Oct 20 '22 at 12:50
6

You can proxy the google analytics script via your own server, save it locally and auto update the file every hour to make sure it's always latest version from google.

I've done this on a couple of sites now and all is working fine.

Google Analytics Proxy Route in NodeJS / MEAN Stack

This is how I implemented it on my blog that's built with the MEAN stack.

router.get('/analytics.js', function (req, res, next) {
    var fileUrl = 'http://www.google-analytics.com/analytics.js';
    var filePath = path.resolve('/content/analytics.js');

    // ensure file exists and is less than 1 hour old
    fs.stat(filePath, function (err, stats) {
        if (err) {
            // file doesn't exist so download and create it
            updateFileAndReturn();
        } else {
            // file exists so ensure it's not stale
            if (moment().diff(stats.mtime, 'minutes') > 60) {
                updateFileAndReturn();
            } else {
                returnFile();
            }
        }
    });

    // update file from remote url then send to client
    function updateFileAndReturn() {
        request(fileUrl, function (error, response, body) {
            fs.writeFileSync(filePath, body);
            returnFile();
        });
    }

    // send file to client
    function returnFile() {
        res.set('Cache-Control', 'public, max-age=' + oneWeekSeconds);
        res.sendFile(filePath);
    }
});

Google Analytics Proxy Action Method in ASP.NET MVC

This is how I implemented it on other sites built with ASP.NET MVC.

public class ProxyController : BaseController
{
    [Compress]
    public ActionResult GoogleAnalytics()
    {
        var fileUrl = "https://ssl.google-analytics.com/ga.js";
        var filePath = Server.MapPath("~/scripts/analytics.js");

        // ensure file exists 
        if (!System.IO.File.Exists(filePath))
            UpdateFile(fileUrl, filePath);

        // ensure file is less than 1 hour old
        var lastModified = System.IO.File.GetLastWriteTime(filePath);
        if((DateTime.Now - lastModified).TotalMinutes > 60)
            UpdateFile(fileUrl, filePath);

        // enable caching for 1 week for page speed score
        Response.AddHeader("Cache-Control", "max-age=604800");

        return JavaScript(System.IO.File.ReadAllText(filePath));
    }

    private void UpdateFile(string fileUrl, string filePath)
    {
        using (var response = WebRequest.Create(fileUrl).GetResponse())
        using (var dataStream = response.GetResponseStream())
        using (var reader = new StreamReader(dataStream))
        {
            var body = reader.ReadToEnd();
            System.IO.File.WriteAllText(filePath, body);
        }
    }
}

This is the CompressAttribute used by the MVC ProxyController for Gzip compression

public class CompressAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {

        var encodingsAccepted = filterContext.HttpContext.Request.Headers["Accept-Encoding"];
        if (string.IsNullOrEmpty(encodingsAccepted)) return;

        encodingsAccepted = encodingsAccepted.ToLowerInvariant();
        var response = filterContext.HttpContext.Response;

        if (encodingsAccepted.Contains("gzip"))
        {
            response.AppendHeader("Content-encoding", "gzip");
            response.Filter = new GZipStream(response.Filter, CompressionMode.Compress);
        }
        else if (encodingsAccepted.Contains("deflate"))
        {
            response.AppendHeader("Content-encoding", "deflate");
            response.Filter = new DeflateStream(response.Filter, CompressionMode.Compress);
        }
    }
}

Updated Google Analytics Script

On the client side I append the analytics path with the current date up to the hour so the browser won't use a cached version more than an hour old.

<!-- analytics -->
<script>
    (function (i, s, o, g, r, a, m) {
        i['GoogleAnalyticsObject'] = r; i[r] = i[r] || function () {
            (i[r].q = i[r].q || []).push(arguments)
        }, i[r].l = 1 * new Date(); a = s.createElement(o),
        m = s.getElementsByTagName(o)[0]; a.async = 1; a.src = g; m.parentNode.insertBefore(a, m)
    })(window, document, 'script', '/analytics.js?d=' + new Date().toISOString().slice(0, 13), 'ga');
</script>
Jason Watmore
  • 4,521
  • 2
  • 32
  • 36
5

For Nginx:

location ~ /analytics.js {
        proxy_pass https://www.google-analytics.com;
        expires 31536000s;
        proxy_set_header Pragma "public";
        proxy_set_header Cache-Control "max-age=31536000, public";
    }

Then change path https://www.google-analytics.com/analytics.js to https://yoursite.com/analytics.js

Savad KP
  • 1,625
  • 3
  • 28
  • 40
5

PHP

Add this in your HTML or PHP code:

<?php if (!isset($_SERVER['HTTP_USER_AGENT']) || stripos($_SERVER['HTTP_USER_AGENT'], 'Speed Insights') === false): ?>
  <script>
    (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
    (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
    m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
    })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

    ga('create', 'UA-PUT YOUR GOOGLE ANALYTICS ID HERE', 'auto');
    ga('send', 'pageview');
  </script>
<?php endif; ?>

JavaScript

This works fine with JavaScript:

  <script>
  if(navigator.userAgent.indexOf("Speed Insights") == -1) {
    (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
    (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
    m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
    })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

    ga('create', 'UA-<PUT YOUR GOOGLE ANALYTICS ID HERE>', 'auto');
    ga('send', 'pageview');
  }
  </script>

NiloVelez already said: Obviously, it won't make any real improvement, but if your only concern is getting a 100/100 score this will do it.

Suriyaa
  • 2,222
  • 2
  • 25
  • 44
1

try this just insert before

<script async='async' src='https://cdn.jsdelivr.net/ga-lite/latest/ga-lite.min.js'></script> <script>var galite=galite||{};galite.UA="xx-xxxxxxx-x";</script>

Please change xx-xxxxxxx-x to your code, please check to implementation here http://www.gee.web.id/2016/11/how-to-leverage-browser-caching-for-google-analitycs.html

Gee
  • 211
  • 2
  • 5
1

In 2020 Page Speed Insights user agents are: "Chrome-Lighthouse" for mobile and "Google Page Speed Insights" for desktop.

<?php if (!isset($_SERVER['HTTP_USER_AGENT']) || stripos($_SERVER['HTTP_USER_AGENT'], 'Chrome-Lighthouse') === false  || stripos($_SERVER['HTTP_USER_AGENT'], 'Google Page Speed Insights') === false): ?>
// your google analytics code and other external script you want to hide from PageSpeed Insights here
<?php endif; ?>
Eduard Dimitrov
  • 152
  • 2
  • 10
0

Google cautions against using local copies of the analtics scripts. However if you are doing it, you will probably want to use local copies of the plugins & the debug script.

A second concern with agressive caching is that you will be getting hits from cached pages - which may have changed or have been removed from the site.

Oren Bochman
  • 1,173
  • 3
  • 16
  • 37
0

To fix this issue you would have to download the file locally and run a cron job to keep updating. Note: this doesn't make your website any faster at all so its best to just ignore it.

For demonstration purposes however, follow this guide: http://diywpblog.com/leverage-browser-cache-optimize-google-analytics/

Mo Beigi
  • 1,614
  • 4
  • 29
  • 50
  • "this doesn't make your website any faster" that's not necessarily true. As in theory, gzipping a non critical concatenated JS a file with analytics included should compress slightly smaller than a separated analytics file because of the shared dictionary. Perhaps more trouble than it's worth. – Ray Foss Feb 22 '16 at 12:57
0

This may do the trick :)

<script>
  $.ajax({
  type: "GET",
  url: "https://www.google-analytics.com/analytics.js",
  success: function(){},
  dataType: "script",
  cache: true
  });
</script>
Nuno Sarmento
  • 415
  • 5
  • 15
0

Depending on your use of Google Analytics data, if you want basic information (such as visits, UI interactions) you might be able to not include analytics.js at all, yet still collect data in GA.

One option may be to instead use the measurement protocol in a cached script. Google Analytics: Measurement Protocol Overview

When you set the transport method explicitly to image, you can see how GA constructs its own image beacons.

ga('set', 'transport', 'image');

https://www.google-analytics.com/r/collect
  ?v={protocol-version}
  &tid={tracking-id}
  &cid={client-id}
  &t={hit-type}
  &dl={location}

You could create your own GET or POST requests with the required payload.

However, if you require a greater level of detail it probably won't be worth your effort.

Jonathan
  • 31
  • 4
  • Where's the connection to Pagespeed? – Nico Haase Dec 19 '17 at 14:22
  • By not loading analytics.js you avoid the pagespeed penalty. – Jonathan Dec 19 '17 at 15:06
  • Yeah. And by skipping all that CSS, JS and imagery out of your page, it will load even faster. Skipping Google Analytics is not an option according to the OP – Nico Haase Dec 19 '17 at 15:10
  • Except that data is still recorded in Google Analytics I think my answer is valid, and clearly stated that depending on the level of detail required from Google Analytics it may be an option worth considering which importantly **would still record visits, UI interactions and potentially other metrics**. If the OP is looking to optimise for the final 1%, it might be an optimisation worth considering. – Jonathan Dec 19 '17 at 15:15
  • @NicoHaase I've edited my comment to hopefully make my point clearer. Interested to hear your thoughts. – Jonathan Dec 20 '17 at 09:46
  • I personally do not like the way of implementing your own logic to collect data for GA. Even if it works for now, you have to adopt your own implementation of doing it if Google changes their code, and if you miss the proper point of doing it, you fail to collect the data. For some companies, this is crucial for business and not something that can stop working until IT fixes their code – Nico Haase Dec 20 '17 at 09:57
  • I agree, but that same logic applies to strategies suggesting taking a copy of analytics.js and hosting it with different caching policies. Looking at the measurement protocol changelog and the frequency of change (even non-breaking change) would make this a valid solution. https://developers.google.com/analytics/devguides/collection/protocol/changelog – Jonathan Dec 20 '17 at 10:39
0

You can set up a cloudfront distribution that has www.google-analytics.com as its origin server and set a longer expiry header in the cloudfront distribution settings. Then modify that domain in the Google snippet. This prevents the load on your own server and the need to keep updating the file in a cron job.

This is setup & forget. So you may want to add a billing alert to cloudfront in case someone "copies" your snippet and steals your bandwidth ;-)

Edit: I tried it and it's not that easy, Cloudfront passes through the Cache-Control header with no easy way to remove it

Jan M
  • 2,205
  • 21
  • 14
0

Open https://www.google-analytics.com/analytics.js file in a new tab, copy all the code.

Now create a folder in your web directory, rename it to google-analytics.

Create a text file in the same folder and paste all the code you copied above.

Rename the file ga-local.js

Now change the URL to call your locally hosted Analytics Script file in your Google Analytics Code. It will look something like this i.e. https://domain.xyz/google-analytics/ga.js

Finally, place your NEW google analytics code into the footer of your webpage.

You are good to go. Now check your website of Google PageSpeed Insights. It will not show the warning for Leverage Browser Caching Google Analytics. And the only problem with this solution is, to regularly update the Analytics Script manually.

FormaL
  • 359
  • 2
  • 5
  • 19
-13

You can minify all your scripts in page, including analytics.js using:

Remember to minify the files before using it. Otherwise it will consume more processing time.

Erick Engelhardt
  • 704
  • 2
  • 10
  • 30