While the ability to capture transfer size for resources in the browser is awesome, in its current state it is an incomplete solution that if reported on FOR EVERYTHING by default could lead to false positives for LUX customers. However, there are some cool ways you can still leverage this for use in LUX.

What is PerformanceResourceTiming.transferSize?

From MDN web docs: 

“The transferSize read-only property represents the size (in octets) of the fetched resource. The size includes the response header fields plus the response payload body (as defined by RFC7230).”

This long awaited feature added to the PerformanceResourceTiming interface allows access to size information from an HTTP request. Additional size information for resources includes both the encoded and decoded body size of the request.

There are some great benefits of getting this information from RUM (vs. synthetic). A few examples: 

  • The ability to provide more understanding around the effective use of browser cache

  • Understand whether you are serving appropriately sized images to the right device

  • Get insight into size anomalies for pages that I may not be looking at with my synthetic testing

That sounds cool? What’s the downside?

We should just capture and report this for all of our resources on a page, right?

Unfortunately, you can’t. transfersize is only provided for resources served by the same domain or resources that pass the timing allow check algorithm. This means that for resources served by a third party, you are dependent on them to list the document’s origin or all (“*”) in the Timing-Allow-Origin header. As of the last check in HTTP Archive, this only included around 20% of resources.

Browser Support is also an issue. As of the writing of this article, there is NO support for transfersize in Safari or iOS Safari. That’s a pretty big deal if you are trying to get this insight for such an overwhelming part of the population that carries an iPhone or uses Safari on their desktop.

If relied on for a look at total page size or size of third-parties, you risk both painting an incomplete picture of page weight as well as a high risk of noise as third parties change their use of Timing-Allow-Origin.

What are some ways I can still use it?

LUX uses transfersize to calculate HTML size (see: What do the different SpeedCurve metrics represent?), but aside from that we aren’t currently leveraging the API for other first class metrics. 

This doesn’t mean you can’t use this to improve your LUX data set! Using customer data, you can capture resource size and send it to LUX.

Here are some examples you can pull from depending on what questions you are trying to answer. Once you capture the data, simply pass it to LUX using the LUX.addData  API.

I need to track and create a performance budget for a single resource:

// Find a specific resource
const script = performance.getEntriesByType("resource").find(resource => === "")

// Use a regular expression to find a specific resource that has a hash or version in the name
const script = performance.getEntriesByType("resource").find(resource => /\/js\/main\.bundle-[a-z0-9]+\.js/.match(

// Log the number of bytes downloaded for this script (usually body + headers)
LUX.addData("main-bundle-transfer-size", script.transferSize)

// Log the size of the compressed script, in bytes
LUX.addData("main-bundle-compressed-size", script.encodedBodySize

// Log the size of the uncompressed script, in bytes
LUX.addData("main-bundle-uncompressed-size", script.decodedBodySize)

// Log the size of the HTTP headers for the script, in bytes
LUX.addData("main-bundle-header-size", script.transferSize - script.encodedBodySize)

// Log the compression ratio of the script as a percentage
LUX.addData("main-bundle-compression-ratio", (1 - script.encodedBodySize / script.decodedBodySize) * 100)

I want to capture size information for a group of assets including "largest size" and "total size".

// Find all resources from <img> tags that came from a specific domain
const firstPartyImages = performance.getEntriesByType("resource").filter(resource => resource.initiatorType === "img" &&"") === 0)

// Of those images, log the size of the largest *downloaded* image, in bytes
// Note: cached resources have a transferSize of 0, so this snippet excludes cached resources
const largestImage = firstPartyImages.sort((a, b) => b.transferSize - a.transferSize)[0]
LUX.addData("largest-first-party-image-size", largestImage.transferSize)

// Of those images, log the size of the largest image, in bytes, *regardless of whether it was cached*
const largestImage = firstPartyImages.sort((a, b) => b.encodedBodySize - a.encodedBodySize)[0]
LUX.addData("largest-first-party-image-size", largestImage.encodedBodySize)

// Log the total bytes *downloaded* for those images, in bytes
const totalBytes = firstPartyImages.reduce((acc, resource) => acc + resource.transferSize, 0)
LUX.addData("total-first-party-image-size", totalBytes)

// Log the total bytes for those images, including those that were cached, in bytes
const totalBytes = firstPartyImages.reduce((acc, resource) => acc + resource.encodedBodySize, 0)
LUX.addData("total-first-party-image-size", totalBytes)

How can I see this data in SpeedCurve?

Once you are passing the data to LUX, setting up the custom metric is quite simple:

  • Go to Settings.

  • Scroll down to the Custom Metrics section and click "Add Custom Metric".

  • Select "Customer Data" as the type of metric.

  • Select "Bytes" as the data type.

  • Specify the customer data variable name. 

  • Pick a human-readable name, for example Largest Image Size.

  • Click "Save Custom Metric". 

Now you can use this metric when creating custom dashboards in favorites, including histograms and time series charts!

We will continue to track the progress and adoption of transfersize and look at ways we can incorporate into LUX in order to add more value for our customers. If you have ideas, please let us know!

Did this answer your question?