What is web performance?
Death by 1000 paper cuts.
Design for performance
Performance should be a fundamental component of design.
Critical Rendering Path
Before the browser can render the page, it needs to construct the DOM and CSSOM trees. As a result, we need to ensure that we deliver both the HTML and CSS to the browser as quickly as possible.
Document Object Model (DOM)
Let’s start with the simplest possible case: a plain HTML page with some text and a single image. How does the browser process this page?
Process: bytes → characters → tokens → nodes → object model
The final output of this entire process is the Document Object Model (DOM) of our page, which the browser uses for all further processing.
Every time the browser processes HTML markup, it goes through all of the steps above: convert bytes to characters, identify tokens, convert tokens to nodes, and build the DOM tree. This entire process can take some time, especially if we have a large amount of HTML to process.
CSS Object Model (CSSOM)
While the browser is constructing the DOM of our page, it encounters a link tag in the head section of the document referencing an external CSS stylesheet: style.css. Anticipating that it needs this resource to render the page, it immediately dispatches a request for it.
As with HTML, we need to convert the received CSS rules into something the browser can understand and work with. Hence, we repeat the HTML process, but for CSS instead of HTML.
The CSS bytes are converted into characters, then tokens, then nodes, and finally they are linked into a tree structure known as the “CSS Object Model” (CSSOM).
Why does the CSSOM have a tree structure? When computing the final set of styles for any object on the page, the browser starts with the most general rule applicable to that node (for example, if it is a child of a body element, then all body styles apply) and then recursively refines the computed styles by applying more specific rules; that is, the rules “cascade down”.
Also note that the CSSOM only contains the styles you decided to override in your stylesheet. Every browser provides a default set of styles - also known as “user-agent styles” - which is what we see when we don’t provide any of our own; our styles simply override these defaults.
Render tree construction, layout and paint
The CSSOM and DOM trees are combined into a render tree, which is then used to compute the layout of each visible element and serves as input to the paint process that renders the pixels to the screen. Optimising each of these steps is critical to achieving optimal rendering performance.
To construct the render tree, the browser roughly does the following:
- Starting at the root of the DOM tree, traverse each visible node. Some nodes are not visible (for example, script tags, meta tags, and so on), and are omitted since they are not reflected in the rendered output. Some nodes are hidden via CSS and are also omitted from the render tree.
- For each visible node, find the appropriate matching CSSOM rules and apply them.
- Emit visible nodes with content and their computed styles.
The final output is a render tree that contains both the content and style information of all the visible content on the screen. With the render tree in place, we can proceed to the layout stage.
Up to this point we’ve calculated which nodes should be visible and their computed styles, but we have not calculated their exact position and size within the viewport of the device - that’s the layout stage, also known as reflow.
The output of the layout process is a “box model”, which precisely captures the exact position and size of each element within the viewport: all of the relative measurements are converted to absolute pixels on the screen.
Finally, now that we know which nodes are visible, and their computed styles and geometry, we can pass this information to the final stage, which converts each node in the render tree to actual pixels on the screen. This step is often referred to as painting or rasterising.
Quick recap of the browser’s steps:
- Process HTML markup and build the DOM tree
- Process CSS markup and build the CSSOM tree
- Combine the DOM and CSSOM into a render tree
- Run layout on the render tree to compute the geometry of each node
- Paint the individual nodes to the screen
Optimising the critical rendering path is the process of minimising the total amount of time spent performing the above sequence.
Render-blocking CSS
By default, CSS is treated as a render-blocking resource, which means the browser won’t render any processed content until the CSSOM is constructed. Make sure to keep your CSS lean, deliver it as quickly as possible, and use media types and queries to unblock rendering.
The critical rendering path requires both the DOM and the CSSOM to construct the render tree. This creates an important performance implication: both HTML and CSS are render-blocking resources.
TL;DR
- By default, CSS is treated as a render-blocking resource
- Media types and media queries allow us to mark some CSS resources as non-render-blocking
- The browser downloads all CSS resources, regardless of blocking or non-blocking behaviour
CSS is a render-blocking resource. Get it to the client as soon and as quickly as possible to optimise the time to first render.
However, what if we have some CSS styles that are only used under certain conditions, for example when the page is being printed or being projected onto a large monitor? It would be nice if we didn’t have to block rendering on these resources.
CSS “media types” and “media queries” allow us to address these use cases:
<link href="style.css" rel="stylesheet">
<link href="print.css" rel="stylesheet" media="print">
<link href="other.css" rel="stylesheet" media="(min-width: 40em)">By using media queries, we can tailor our presentation to specific use cases - such as display versus print - and also to dynamic conditions such as changes in screen orientation, resize events, and more. When declaring your stylesheet assets, pay close attention to the media type and queries; they greatly impact critical rendering path performance.
Let’s consider some examples:
<link href="style.css" rel="stylesheet">
<link href="style.css" rel="stylesheet" media="all">
<link href="portrait.css" rel="stylesheet" media="orientation:portrait">
<link href="print.css" rel="stylesheet" media="print">- The first declaration is render-blocking and matches in all conditions.
- The second declaration is also render-blocking: “all” is the default type so if you don’t specify any type, it’s implicitly set to “all”. Hence, the first and second declarations are equivalent.
- The third declaration has a dynamic media query, which is evaluated when the page is loaded. Depending on the orientation of the device while the page is loading,
portrait.cssmay or may not be render-blocking. - The last declaration is only applied when the page is being printed, so it is not render-blocking when the page is first loaded in the browser.
Finally, note that “render-blocking” only refers to whether the browser has to hold the initial rendering of the page on that resource. In either case, the browser still downloads the CSS asset, albeit with a lower priority for non-blocking resources.
Adding interactivity with JavaScript
To deliver optimal performance, make your JavaScript async and eliminate any unnecessary JavaScript from the critical rendering path.
TL;DR:
- JavaScript can query and modify the DOM and the CSSOM
- JavaScript execution blocks on the CSSOM
- JavaScript blocks DOM construction unless explicitly declared as
async
When the HTML parser encounters a script tag, it pauses its process of constructing the DOM and yields control to the JavaScript engine; after the JavaScript engine finishes running, the browser picks up where it left off and resumes DOM construction.
Executing inline script blocks DOM construction, which also delays the initial render.
Another subtle property of introducing scripts into our page is that they can read and modify not just the DOM, but also CSSOM properties.
What if the browser hasn’t finished downloading and building the CSSOM when we want to run our script to modify CSSOM properties? The answer is simple and not very good for performance: the browser delays script execution and DOM construction until it has finished downloading and constructing the CSSOM.
In short, JavaScript introduces a lot of new dependencies between the DOM, the CSSOM, and JavaScript execution. This can cause significant delays in processing and rendering the page on the screen:
- The location of the script in the document is significant
- When the browser encounters a script tag, DOM construction pauses until the script finishes executing
- JavaScript can query and modify the DOM and the CSSOM
- JavaScript execution pauses until the CSSOM is ready
To a large degree, “optimising the critical rendering path” refers to understanding and optimising the dependency graph between HTML, CSS and JavaScript.
Parser-blocking versus asynchronous JavaScript
By default, JavaScript execution is “parser-blocking”: when the browser encounters a script in the document it must pause DOM construction, hand control to the JavaScript runtime, and let the script execute before proceeding with DOM construction. In fact, inline scripts are always parser-blocking unless you write additional code to defer their execution.
In the case of an external JavaScript file the browser must pause to wait for the script to be fetched from disk, cache, or a remote server, which can add tens to thousands of milliseconds of delay to the critical rendering path.
async is the signal to the browser that the script does not need to be executed at the exact point where it’s referenced. This allows the browser to continue to construct the DOM and let the script execute when it is ready - for example, after the file is fetched from cache or a remote server.
Monitoring the Critical Rendering Path
The foundation of every solid performance strategy is good measurement and instrumentation. You can’t optimise what you can’t measure. There are two approaches for measuring CRP:
- The Lighthouse approach runs a series of automated tests against a page and then generates a report on the page’s CRP performance. This approach provides a quick and easy high-level overview of CRP performance for a particular page loaded in your browser, allowing you to rapidly test, iterate, and improve its performance.
- The Navigation Timing API approach captures Real User Monitoring (RUM) metrics. As the name implies, these metrics are captured from real user interactions with your site and provide an accurate view into real-world CRP performance, as experienced by your users across a variety of devices and network conditions.
In general, a good approach is to use Lighthouse to identify obvious CRP optimisation opportunities, and then instrument your code with the Navigation Timing API to monitor how your app performs in the wild.
You can use the Critical Rendering Chain to improve your CRP by:
- Minimising the number of critical resources: eliminating them, deferring their download, marking them as async, and so on
- Optimising the number of critical bytes to reduce the download time (number of roundtrips)
- Optimising the order in which the remaining critical resources are loaded: downloading all critical assets as early as possible to shorten the critical path length
Instrumenting your code with the Navigation Timing API
The combination of the Navigation Timing API and other browser events emitted as the page loads allows you to capture and record the real-world CRP performance of any page.
domLoading- the starting timestamp of the entire process; the browser is about to start parsing the first received bytes of the HTML document.domInteractive- marks the point when the browser has finished parsing all of the HTML and DOM construction is complete.domContentLoaded- marks the point when both the DOM is ready and there are no stylesheets blocking JavaScript execution, meaning we can now (potentially) construct the render tree.
Many JavaScript frameworks wait for this event before they start executing their own logic. For this reason the browser captures the EventStart and EventEnd timestamps to allow us to track how long this execution took.
domComplete- as the name implies, all of the processing is complete and all of the resources on the page (images, etc.) have finished downloading. In other words, the loading spinner has stopped spinning.loadEvent- as a final step in every page load the browser fires an onload event which can trigger additional application logic.
The HTML specification dictates specific conditions for each event: when it should be fired, which conditions should be met, and so on. For our purposes, we’ll focus on a few key milestones related to the critical rendering path:
domInteractivemarks when the DOM is readydomContentLoadedtypically marks when both the DOM and CSSOM are ready- If there is no parser-blocking JavaScript then
DOMContentLoadedwill fire immediately afterdomInteractive.
- If there is no parser-blocking JavaScript then
domCompletemarks when the page and all of its sub-resources are ready.
Analysing critical rendering path performance
We can construct the render tree and even paint the page without waiting for each asset on the page: not all resources are critical to deliver the first paint quickly. In fact, when we talk about the critical rendering path we are typically talking about HTML markup, CSS, and JavaScript. Images do not block the initial render of the page - although we should also try to get the images painted as soon as possible.
That said, the load event (also known as onload) is blocked on images. The onload event marks the point at which all resources that the page requires have been downloaded and processed; at this point, the loading spinner can stop spinning in the browser.
When a page contains references to a CSS file and a parser-blocking JavaScript file, the domContentLoaded event is blocked until the CSS file is downloaded and parsed: because the JavaScript might query the CSSOM, we must block on the CSS file until it downloads before we can execute JavaScript.
What if we replace our external script with an inline script? Even if the script is inlined directly into the page, the browser can’t execute it until the CSSOM is constructed. In short, inlined JavaScript is also parser-blocking.
As soon as the browser hits the script tag, it blocks and waits until the CSSOM is constructed. In most cases the browser downloads both CSS and JavaScript in parallel and they may finish downloading at about the same time. In this instance, inlining the JavaScript code doesn’t help us much. But there are several strategies that can make our page render faster:
- For external scripts we can add the
asynckeyword to unblock the parser - Inline CSS and JavaScript to reduce the number of network calls
Performance patterns
Let’s define the vocabulary we use to describe the critical rendering path:
- Critical resource - a resource that could block initial rendering of the page
- Critical path length - the number of roundtrips, or the total time required to fetch all the critical resources
- Critical bytes - the total number of bytes required to get to the first render of the page, which is the sum of the transfer file sizes of all critical resources
Example 1
The simplest possible page consists of just the HTML markup; no CSS, no JavaScript, no other resources. To render this page the browser has to initiate the request, wait for the HTML document to arrive, parse it, build the DOM, and then finally render it on the screen.
In the best case (if the HTML file is small), just one network roundtrip can fetch the entire document. Due to how the TCP transport protocol works, larger files may require more roundtrips. As a result, in the best case a web page has a one-roundtrip (minimum) critical rendering path.
Example 2
Once again, we incur a network roundtrip to fetch the HTML document, and then the retrieved markup tells us that we also need the CSS file; this means that the browser has to go back to the server and get the CSS before it can render the page on the screen. As a result, this page incurs a minimum of two roundtrips before it can be displayed. Once again, the CSS file may take multiple roundtrips, hence the emphasis on minimum.
With the above example, we have:
- 2 critical resources
- 2 or more roundtrips for the minimum critical path length
- 9 KB of critical bytes
We need both the HTML and CSS to construct the render tree. As a result, both HTML and CSS are critical resources: the CSS is fetched only after the browser gets the HTML document, hence the critical path length is at minimum two roundtrips. Both resources add up to a total of 9 KB of critical bytes.
Example 3
We added app.js, which is both an external JavaScript asset on the page and a parser-blocking (that is, critical) resource. Worse, in order to execute the JavaScript file we have to block and wait for the CSSOM; recall that JavaScript can query the CSSOM and hence the browser pauses until style.css is downloaded and the CSSOM is constructed.
That said, in practice if you look at this page’s “network waterfall,” you’ll see that both the CSS and JavaScript requests are initiated at about the same time; the browser gets the HTML, discovers both resources, and initiates both requests. As a result, the page above has the following critical-path characteristics:
- 3 critical resources
- 2 or more roundtrips for the minimum critical path length
- 11 KB of critical bytes
We now have three critical resources that add up to 11 KB of critical bytes, but our critical path length is still two roundtrips because we can transfer the CSS and JavaScript in parallel. Figuring out the characteristics of your critical rendering path means being able to identify the critical resources and also understanding how the browser will schedule their fetches.
Let’s say we realised that the JavaScript we included on our page doesn’t need to be blocking; we have some analytics and other code in there that doesn’t need to block the rendering of our page. With that knowledge, we can add the async attribute to the script tag to unblock the parser.
An asynchronous script has several advantages:
- The script is no longer parser-blocking and is not part of the critical rendering path
- Because there are no other critical scripts, the CSS doesn’t need to block the
domContentLoadedevent - The sooner the
domContentLoadedevent fires, the sooner other application logic can begin executing - As a result, our optimised page is now back to two critical resources (HTML and CSS), with a minimum critical path length of two roundtrips, and a total of 9 KB of critical bytes
Finally, if the CSS stylesheet were only needed for print, how would that look?
<link href="style.css" rel="stylesheet" media="print">Because the style.css resource is only used for print, the browser doesn’t need to block on it to render the page. Hence, as soon as DOM construction is complete, the browser has enough information to render the page. As a result, this page has only a single critical resource (the HTML document), and the minimum critical rendering path length is one roundtrip.
Optimising the Critical Rendering Path
To deliver the fastest possible time to first render, we need to minimise three variables:
- The number of critical resources
- The critical path length
- The number of critical bytes
A critical resource is a resource that could block initial rendering of the page. The fewer of these resources, the less work for the browser, the CPU, and other resources.
Similarly, the critical path length is a function of the dependency graph between the critical resources and their byte size: some resource downloads can only be initiated after a previous resource has been processed, and the larger the resource the more roundtrips it takes to download.
Finally, the fewer critical bytes the browser has to download, the faster it can process content and render it visible on the screen. To reduce the number of bytes, we can reduce the number of resources (eliminate them or make them non-critical) and ensure that we minimise the transfer size by compressing and optimising each resource.
The general sequence of steps to optimise the critical rendering path is:
- Analyse and characterise your critical path: number of resources, bytes, length
- Minimise the number of critical resources: eliminate them, defer their download, mark them as async, and so on
- Optimise the number of critical bytes to reduce the download time (number of roundtrips)
- Optimise the order in which the remaining critical resources are loaded: download all critical assets as early as possible to shorten the critical path length (preload)
PageSpeed rules and recommendations
Eliminate render-blocking JavaScript and CSS
To deliver the fastest time to first render, minimise and (where possible) eliminate the number of critical resources on the page, minimise the number of downloaded critical bytes, and optimise the critical path length.
Optimise JavaScript use
JavaScript resources are parser-blocking by default unless marked as async or added via a special JavaScript snippet. Parser-blocking JavaScript forces the browser to wait for the CSSOM and pauses construction of the DOM, which in turn can significantly delay the time to first render.
Avoid synchronous server calls
Use the navigator.sendBeacon() method to limit data sent by XMLHttpRequests in unload handlers. Because many browsers require such requests to be synchronous, they can slow page transitions, sometimes noticeably. The following code shows how to use navigator.sendBeacon() to send data to the server in the pagehide handler instead of in the unload handler.
<script>
(function() {
window.addEventListener('pagehide', logData, false);
function logData() {
navigator.sendBeacon(
'https://putsreq.herokuapp.com/Dt7t2QzUkG18aDTMMcop',
'Sent by a beacon!');
}
})();
</script>The fetch() method provides an easy way to asynchronously request data. Because it is not available everywhere yet, you should use feature detection to test for its presence before use. This method processes responses with promises rather than multiple event handlers. Unlike the response to an XMLHttpRequest, a fetch response is a stream object starting in Chrome 43. This means that a call to json() also returns a promise.
<script>
fetch('./api/some.json')
.then(
function(response) {
if (response.status !== 200) {
console.log('Looks like there was a problem. Status Code: ' + response.status);
return;
}
response.json().then(function(data) {
console.log(data);
});
}
)
.catch(function(err) {
console.log('Fetch Error :-S', err);
});
</script>The fetch() method can also handle POST requests:
<script>
fetch(url, {
method: 'post',
headers: {
"Content-type": "application/x-www-form-urlencoded; charset=UTF-8"
},
body: 'foo=bar&lorem=ipsum'
}).then(function() {
// Additional code
});
</script>Defer parsing JavaScript
To minimise the amount of work the browser has to perform to render the page, defer any non-essential scripts that are not critical to constructing the visible content for the initial render.
Avoid long-running JavaScript
Long-running JavaScript blocks the browser from constructing the DOM, CSSOM and rendering the page. Defer until later any initialisation logic and functionality that is non-essential for the first render. If a long initialisation sequence needs to run, consider splitting it into several stages to allow the browser to process other events in between.
Optimise CSS use
CSS is required to construct the render tree, and JavaScript often blocks on CSS during initial construction of the page. Ensure that any non-essential CSS is marked as non-critical (for example, print and other media queries), and that the amount of critical CSS and the time to deliver it are as small as possible.
Put CSS in the document head
Specify all CSS resources as early as possible within the HTML document so that the browser can discover the <link> tags and dispatch the request for the CSS as soon as possible.
Avoid CSS imports
The CSS import (@import) directive enables one stylesheet to import rules from another stylesheet file. However, avoid these directives because they introduce additional roundtrips into the critical path: the imported CSS resources are discovered only after the stylesheet with the @import rule itself is received and parsed.
Inline render-blocking CSS
For best performance, you may want to consider inlining the critical CSS directly into the HTML document. This eliminates additional roundtrips in the critical path, and if done correctly can deliver a one-roundtrip critical path length where only the HTML is a blocking resource.
Notable page lifecycle events
DOMContentLoaded
The DOMContentLoaded event is fired when the initial HTML document has been completely loaded and parsed, without waiting for stylesheets, images, and sub-frames to finish loading. load should only be used to detect a fully-loaded page. It is an incredibly popular mistake to use load where DOMContentLoaded would be much more appropriate, so be cautious.
Fonts
The font-display property adjusts the way a custom font is applied. By default it’s set to auto, and in all major browsers this means the browser will wait 3 seconds for the custom font to download before falling back to the next available font.
JavaScript
In 2019, the dominant costs of processing scripts are download and CPU execution time.
What does this mean for web developers? Parse and compile costs are no longer as slow as we once thought. The three things to focus on for JavaScript bundles are:
- Improve download time
- Keep your JavaScript bundles small, especially for mobile devices. Small bundles improve download speeds, lower memory usage, and reduce CPU costs.
- Avoid having just a single large bundle; if a bundle exceeds ~50-100 kB, split it up into separate smaller bundles. (With HTTP/2 multiplexing, multiple request and response messages can be in flight at the same time, reducing the overhead of additional requests.)
- On mobile you’ll want to ship much less, especially because of network speeds, but also to keep memory usage low.
- For most real-world connection speeds, V8 parses faster than the download, so V8 is done parsing and compiling a few milliseconds after the last script bytes are downloaded.
- Improve execution time
- Avoid long tasks that can keep the main thread busy and push out how soon pages become interactive. Post-download, script execution time is now a dominant cost.
- Avoid large inline scripts (they’re still parsed and compiled on the main thread). A good rule of thumb: if the script is over 1 kB, avoid inlining it (also because 1 kB is when code caching kicks in for external scripts).
Optimisations
Code splitting
Modern sites often combine all of their JavaScript into a single, large bundle. When JavaScript is served this way, loading performance suffers. Large amounts of JavaScript can also tie up the main thread, delaying interactivity. This is especially true of devices with less memory and processing power.
An alternative to large bundles is code splitting, where JavaScript is split into smaller chunks. This enables sending the minimal code required to provide value upfront, improving page-load times. The rest can be loaded on demand.
Tools available: Webpack, Parcel.
Code coverage
Even if you use code splitting in your app, you may still find some unused code being loaded on pages. Tree shaking could be part of the solution to eliminating that unused code.
Tree shaking
(To expand later.)
Get rid of unnecessary polyfills (e.g. promise)
(To expand later.)
JSON.parse() is much faster than JavaScript object literals
Tools
- Source Map Explorer
- Webpack Bundle Analyzer
lighthouse-cliSpeedcurveandCalibre
WebPageTest
By establishing the synthetic test in a way that simulates a real user accessing your website, you’re better able to identify performance issues that are actually affecting real users. The last thing you want to do is spend time fixing a problem that doesn’t even exist, so avoid polluting test results with costly distractions.
When a user revisits a page configured to cache its resources, the browser is able to make use of the local copy and avoid downloading the resources all over again. This is great for performance because, as the saying goes, the fastest network request is the one you never even have to make. The cache state is such an important factor precisely because it’s so impactful. A page loaded with a warm cache is usually much faster than its cold counterpart. If users typically hit a page with a warm cache, over-analysing WebPageTest’s first-view results is less likely to lead to meaningful insights.
Imagine an Australian-based website being tested from the default WebPageTest agent in the US. If the majority of traffic originates from the US, this might make sense. However, if the website primarily serves Australians, then the latency overhead just to make one roundtrip would be a distraction from the actual performance issues.
When you select pages to use for testing, popularity should not be the only factor. You must also consider pages on which performance is a business-critical requirement. For example, an e-commerce website may want to audit the performance of the product pages or the checkout flow. These pages may not show up in the analytics report as some of the most-frequented pages on the entire site, but that should not diminish the importance of page-loading performance, due to their direct business impact.
With the assurance that your tests are accurate representations of live traffic, scrutiny of performance issues that manifest themselves in synthetic test results carries more weight. Optimising the synthetic experience will more likely translate to an optimised real-user experience when you’re able to focus on the issues that actually affect them.
WebPageTest mobile support falls under two categories: emulation and native.
By using flow view on pages that are not usually visited directly, test results will more accurately reflect the real-user experience. Remember to look at your RUM report to determine common entry pages and configure your flow views accordingly.
#
# Place the script below in the textarea of the `script`
# tab of WebPageTest to create a flow view
#
logData 0
navigate http://www.example.com
logData 1
navigate http://www.example.com/testWebPageTest provides three ways to authenticate a user:
- HTTP basic authentication
- DOM manipulation
- Setting cookies
Performance improvement
Performance regression
A recommended approach for continuously improving web performance is to detect regressions in CI and roll them back before they reach users.
Introducing performance budget
(To expand later.)
Use the Long Tasks API to find out how third-party scripts behave
- Collected by mPulse
- Is it possible to see the details?
Use GZip
State of the web (HTTP Archive)
- Average bytes per page by content type
Use HTTP/2 Server Push
- Use a CDN that supports HTTP/2 to serve static assets, e.g. pushing fonts and some JavaScript files to the clients.
Gotchas
- HTTP/2 and Subresource Integrity (SRI) don’t always get on (source)
- How to generate SRI digest?
Use ServiceWorker
One feature of service workers is the ability to save files to the cache when the service worker is installing. This is referred to as precaching.
Use rel=preconnect HTTP header
rel=preconnect is often recommended for origins that have important resources but can’t be discovered by the browser’s preloader.
Performance budgets
A performance budget is a set of limits imposed on metrics that affect site performance. This could be the total size of a page, the time it takes to load on a mobile network, or even the number of HTTP requests that are sent. Defining a budget helps get the web performance conversation started. It serves as a point of reference for making decisions about design, technology, and adding features.
Having a budget enables designers to think about the effects of high-resolution images and the number of web fonts they pick. It also helps developers compare different approaches to a problem and evaluate frameworks and libraries based on their size and parsing cost.
User-centric performance metrics
First Contentful Paint
FCP measures the time from Navigation Start until the browser paints content to the screen. In most cases, not until after the stylesheets are downloaded and processed.
Time to Interactive
TTI measures the time from Navigation Start until the page’s resources are loaded and the main thread is idle (for at least 5 seconds).
References
- Web Perf 101
- The cost of JavaScript in 2019
- Tools for generating SRI hashes:
- Use
openssl:cat FILENAME.js | openssl dgst -sha384 -binary | openssl base64 -A - SRI Hash Generator
- Use