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:
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.
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.
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.
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>
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; ?>
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