Skip to main content Skip to docs navigation

Techniques

Learn async scripts for asm.js, optimizing startup performance, WebRTC, audio, 2D collision detection and tilemaps techniques to develop games using open web technologies.

On this page

Async scripts for asm.js

Every medium or large game should compile asm.js code as part of an async script to give the browser the maximum flexibility to optimize the compilation process. In Gecko, async compilation allows the JavaScript engine to compile the asm.js off the main thread when the game is loading and cache the generated machine code so that the game doesn't need to be compiled on subsequent loads (starting in Firefox 28). To see the difference, toggle javascript.options.parallel_parsing in about:config .

Putting async into action

Getting async compilation is easy: when writing your JavaScript, just use the async attribute like so:

html
                                        
                                            
                                                
                                                    <
                                                    script
                                                
                                                async
                                                src
                                                
                                                    =
                                                    "
                                                    file.js"
                                                
                                                >
                                            
                                            
                                            
                                                
                                                    </
                                                    script
                                                
                                                >
                                            
                                        
                                    

or, to do the same thing via script:

js
                                        
                                            const
                                            script =
                                            document.
                                            createElement
                                            (
                                            "script"
                                            )
                                            ;
                                            script.
                                            src =
                                            "file.js"
                                            ;
                                            document.
                                            body.
                                            appendChild
                                            (
                                            script)
                                            ;
                                        
                                    

(Scripts created from script default to async .) The default HTML shell Emscripten generates produces the latter.

When is async not async?

Two common situations in which a script is *not* async (as defined by the HTML spec ) are:

html
                                        
                                            
                                                
                                                    <
                                                    script
                                                
                                                async
                                                >
                                            
                                            
                                                
                                                    code
                                                    (
                                                    )
                                                    ;
                                                
                                            
                                            
                                                
                                                    </
                                                    script
                                                
                                                >
                                            
                                        
                                    

and

js
                                        
                                            const
                                            script =
                                            document.
                                            createElement
                                            (
                                            "script"
                                            )
                                            ;
                                            script.
                                            textContent =
                                            "code()"
                                            ;
                                            document.
                                            body.
                                            appendChild
                                            (
                                            script)
                                            ;
                                        
                                    

Both are counted as 'inline' scripts and get compiled and then run immediately.

What if your code is in a JS string? Instead of using eval or innerHTML , both of which trigger synchronous compilation, you should use a Blob with an object URL:

js
                                        
                                            const
                                            blob =
                                            new
                                            Blob
                                            (
                                            [
                                            codeString]
                                            )
                                            ;
                                            const
                                            script =
                                            document.
                                            createElement
                                            (
                                            "script"
                                            )
                                            ;
                                            const
                                            url =
                                            URL
                                            .
                                            createObjectURL
                                            (
                                            blob)
                                            ;
                                            script.
                                            onload =
                                            script.
                                            onerror
                                            =
                                            (
                                            )
                                            =>
                                            URL
                                            .
                                            revokeObjectURL
                                            (
                                            url)
                                            ;
                                            script.
                                            src =
                                            url;
                                            document.
                                            body.
                                            appendChild
                                            (
                                            script)
                                            ;
                                        
                                    

The setting of src rather than innerHTML is what makes this script async.

Optimizing startup performance

Improving your startup performance is often one of the highest value performance optimizations that can be made. How long does your app take to start up? Does it seem to lock up the device or the user's browser while the app loads? That makes users worry that your application has crashed, or that something else is wrong. Good user experience includes ensuring your app loads quickly. This article provides performance tips and suggestions for both writing new applications and porting applications to the web from other platforms.

Fast asynchronous loading

Regardless of platform, it's always a good idea to start up as quickly as possible. Since that's a universal issue, we won't be focusing on it too much here. Instead, we're going to look at a more important issue when building Web apps: starting up as asynchronously as possible. That means not running all your startup code in a single event handler on the app's main thread.

Rather, create a Web worker that does as much as possible in a background thread (for example, fetching and processing data.) Relegating tasks to a Web worker frees up the main thread for tasks requiring it, like user events and rendering UI. In turn, main thread events should consist of many small tasks, also known as micro tasks , rather than larger, more time consuming tasks.

Asynchronous loading helps prevent pages and user interfaces from appearing to be (or actually becoming) unresponsive. By minimizing the time required for any individual loading task, the application's event loop will continue to cycle while it starts up. This will prevent the application, browser, and/or device from appearing frozen.

In the worst case, blocking the main thread can cause users to uninstall your app; for example, if someone launches your app by mistake and they aren't prevented from closing the application, they may want to take action so that doesn't accidentally happen again.

Where there's a will…

It is easier to just write everything the "right way" the first time then it is to retrofit for performance (and accessibility). When you are starting from scratch, making appropriate bits of code asynchronous means a retrofit isn't necessary. All pure startup calculations should be performed in background threads, while you keep the run-time of main thread events as short as possible. Instead of including a progress indicator so the user knows what's going on and how long they'll be waiting, make the progress bar unnecessary.

On the other hand, porting an existing app to the Web can be challenging. Native application don't need to be written in an asynchronous fashion because the operating system usually handles loading for you. The source application might have a main loop that can easily be made to operate asynchronously (by running each main loop iteration separately); startup is often just a continuous, monolithic procedure that might periodically update a progress meter.

While you can use Web workers to run even very large, long-duration chunks of JavaScript code asynchronously, there's a huge caveat: Web workers can't directly manipulate the DOM and have limited access to methods and properties of the window object, including no access to WebGL . This all means that unless you can easily pull out the "pure calculation" chunks of your startup process into workers, you'll wind up having to run most or all of your startup code on the main thread.

However, even code like that can be made asynchronous, with a little work.

Getting asynchronous

Here are some suggestions for how to build your startup process to be as asynchronous as possible (whether it's a new app or a port):

  • Use the defer or async attribute on script tags needed by the Web application. This allows HTML parsers to continue processing the document, instead of having to wait until the scripts have been downloaded and executed before continuing.
  • If you need to decode asset files (for example, decoding JPEG files and turning them into raw texture data for later use by WebGL), that's great to do in workers.
  • When dealing with data supported by the browser (for example, decoding image data), use the decoders built into the browser or device rather than rolling your own or using one from the original codebase. The provided one is almost certainly significantly faster, and will reduce your app size to boot. In addition, the browser may automatically parallelize these decoders.
  • Any data processing that can be done in parallel should be. Don't do one chunk of data after another; do them all at once when possible!
  • Don't include scripts or stylesheets that don't participate in the critical rendering path in your startup HTML file. Load them only when needed.
  • Reduce the size of your JavaScript files. Try to send the minified version of the file to the browser and use compression like Gzip or Brotli.
  • Utilize resource hints (like preconnect or preload) whenever possible to indicate to the browser which files are more critical for your application.

The more stuff you can do asynchronously, the better advantage your app can take of multicore processors.

Porting issues

Once the initial loading is done and the app's main code starts to run, it's possible your app has to be single-threaded, especially if it's a port. The most important thing to do to try to help with the main code's startup process is to refactor the code into small pieces. These can then be executed in chunks interspersed across multiple calls to your app's main loop (so that the main thread gets to handle input and the like).

Emscripten provides an API to help with this refactoring; for example, you can use emscripten_push_main_loop_blocker() to establish a function to be executed before the main thread is allowed to continue. By establishing a queue of functions to be called in sequence, you can more easily manage running bits of code without blocking the main thread.

That leaves, though, the problem of having to refactor your existing code to actually work that way. That can take some time.

How asynchronous should I get?

The faster your site first becomes usable and the more responsive it is to user input, the better it will be perceived. A site that takes 1 or 2 seconds before content first appears is usually seen as fast; if you're used to sites taking 3 or 4 seconds, then 7 or 8 seconds feels like a very long time.

In terms of responsiveness, users won't notice a delay of 50ms or less. Any delay of over 200ms and the user will perceive your site as sluggish. When working to improve the loading and responsiveness of your applications, remember that many of your users may have older, slower computer than yours, they may experience longer delays than you do!

Other suggestions

There are other things beyond going asynchronous, which can help you improve your app's startup time. Here are a few of them:

Download time

Keep in mind how long it will take the user to download your application's data. If your application is very popular, or has to re-download content frequently, you should try to have as fast a hosting server as possible. Always compress your data to make it as small as you can.

Data size

Do your best to optimize the size of your data; smaller level files will download and be processed faster than larger ones.

Subjective factors

Anything you can do to help keep the user engaged during the startup process will help make the time seem to go by faster. Displaying a mock splash screen can improve perceived performance . For heavy sites, anything you can do to help the user feel like your app is doing something instead of sitting there quietly helps.

See also

Performance Monitoring: RUM vs. synthetic monitoring

Synthetic monitoring and real user monitoring (RUM) are two approaches for monitoring and providing insight into web performance. RUM and synthetic monitoring provide for different views of performance and have benefits, good use cases and shortfalls. RUM is generally best suited for understanding long-term trends whereas synthetic monitoring is very well suited to regression testing and mitigating shorter-term performance issues during development. In this article we define and compare these two performance monitoring approaches.

Synthetic Monitoring

Synthetic monitoring involves monitoring the performance of a page in a 'laboratory' environment, typically with automation tooling in a consistent as possible environment. Synthetic Monitoring involves deploying scripts to simulate the path an end user might take through a web application, reporting back the performance the simulator experiences. The traffic measured is not of your actual users, but rather synthetically generated traffic collecting data on page performance.

An example of synthetic monitoring is WebPageTest.org . It is done in a controlled environment where variable like geography, network, device, browser, and cached status are predetermined. It provides waterfall charts for every asset served by the host and CDN as well as every third party asset and asset requests generated by all third party scripts, such as ads and analytic services.

Controlling for environmental variables is helpful in understanding where performance bottlenecks have been occurring and identifying the source of any performance issues. For example, but it isn't reflective of the actual experience of users, especially the long tail.

Synthetic monitoring can be an important component of regression testing and production site monitoring. Test the site at every stage of development and regularly in production. Changes from baseline performance as part of continuous integration should fail a push. If an issue arises in production, synthetic monitoring can provide insight, helping identify, isolate, and resolve problems before they negatively user experience.

Real User Monitoring

Real User Monitoring or RUM measures the performance of a page from real users' machines. Generally, a third party script injects a script on each page to measure and report back on page load data for every request made. This technique monitors an application's actual user interactions. In real user monitoring, the browsers of real users report back performance metrics experienced. RUM helps identify how an application is being used, including the geographic distribution of users and the impact of that distribution on the end user experience.

Unlike Synthetic monitoring, RUM captures the performance of actual users regardless of device, browser, network or geographic location. As users interact with an application, all performance timings are captured, regardless of what actions are taken or pages viewed. RUM monitors actual use cases, not the synthetic, assumed use cases predefined by an engineer, PM, or marketing team. This is particularly important for large sites or complex apps, where the functionality or content is constantly changing, and where the population accessing the application may differ greatly in life experiences from those creating it.

By leveraging RUM, a business can better understand its users and identify the areas on its site that require the most attention. Moreover, RUM can help to understand the geographic or channel distribution trends of your users. Knowing your user trends helps you better define your business plan and, from a monitoring perspective, allows you to identify key areas to target for optimization and performance improvements.

RUM v Synthetic

Synthetic is well suited for catching regressions during development life cycles, especially with network throttling . It is fairly easy, inexpensive, and great for spot-checking performance during development as an effective way to measure the effect of code changes, but it doesn't reflect what real users are experiencing and provides only a narrow view of performance.

RUM, on the other hand, provides real metrics from real users using the site or application. While this is more expensive and likely less convenient, it provides vital user experience data.

Performance APIs

There are many monitoring services. If you do want to roll your own monitoring system, take a look at the performance APIs, mainly PerformanceNavigationTiming and PerformanceResourceTiming , but also PerformanceMark , PerformanceMeasure , and PerformancePaintTiming .

Performance budgets

A performance budget is a limit to prevent regressions. It can apply to a file, a file type, all files loaded on a page, a specific metric (e.g. Time to Interactive ), a custom metric (e.g. Time to Hero Element), or a threshold over a period of time.

Why do I need a performance budget?

A budget exists to reflect your reachable goals. It's a tradeoff between user experience, against other performance indicators (e.g. conversion rate).

These goals can be:

Their primary goal is to prevent regressions, but they can provide insights to forecast trends (i.e. On September, 50% of the budget was spent in a week).

Additionally, it can uncover development needs (i.e. A large library with smaller alternatives is often picked to solve a common problem).

How do I define a performance budget?

A budget should include 2 levels:

  • Warning.
  • Error.

The warning level allows you to be proactive and plan any tech debt, while not blocking development or deploys.

The error level is an upper bound limit, where changes will have a negative and noticeable impact.

In order to begin, you need to first measure the devices and connection speeds where your users are coming from (e.g. A ~$200 Android device over a 3G connection), using multiple tools . These time-based metrics will translate into file-size budgets.

A default baseline to reduce bounce rate is to achieve Time to Interactive under 5 seconds in 3G/4G, and under 2 seconds for subsequent loads . However, based on the specific goals and content of your site, you might choose to focus on other metrics.

For a text-heavy site such as a blog or a news site, the First Contentful Paint metric could reflect more closely the user behavior. (i.e. How fast can users start reading), which will inform file specific budgets (e.g. Font size), and their optimizations. (e.g. Using font-display to improve Perceived Performance ).

The ultimate value of a Performance Budget is to correlate the impact of Performance on business or product goals. When defining metrics, you should focus on User Experience , which will dictate not only the bounce or conversion rate but how likely is that user to return.

How do I implement a performance budget?

During development, there are a few tools to run checks against new or modified assets:

  • A module bundler (e.g. webpack ), has performance features that will notify you when assets exceed specified limits.
  • Bundlesize , allows you to define and run file size checks in your continuous integration (CI) pipeline.

File size checks are the first line of defense against regressions but translating size back into time metrics can be difficult since development environments could be missing 3rd party scripts, and optimizations commonly provided by a CDN .

The first step is to define a development baseline for each branch to compare to and the precision of the difference between development and production can be used as a goal towards better match the live environment.

The Lighthouse Bot integrates with Travis CI and can be used to gather Lighthouse and Webpage Test metrics from a development URL. The bot will pass or fail based on the provided minimum scores.

How do I enforce a performance budget?

The sooner that you can identify a potential addition pushing the budget, the better you can analyze the current state of your site, and pinpoint optimizations or unnecessary code.

However, you should have multiple budgets and be dynamic. They are meant to reflect your ongoing goals but allow risks and experiments. For example, you may introduce a feature that increases overall load time but attempts to increase user engagement. (i.e. How long a user stays on a page or site).

A performance budget helps you protect optimal behavior for your current users while enabling you to tap into new markets and deliver custom experiences.

See also

Performance fundamentals

Performance means efficiency. In the context of Open Web Apps, this document explains in general what performance is, how the browser platform helps improve it, and what tools and processes you can use to test and improve it.

What is performance?

Ultimately, user-perceived performance is the only performance that matters. Users provide inputs to the system through touch, movement, and speech. In return, they perceive outputs through sight, touch, and hearing. Performance is the quality of system outputs in response to user inputs.

All other things being equal, code optimized for some target besides user-perceived performance (hereafter UPP) loses when competing against code optimized for UPP. Users prefer, say, a responsive, smooth app that only processes 1,000 database transactions per second, over a choppy, unresponsive app that processes 100,000,000 per second. Of course, it's by no means pointless to optimize other metrics, but real UPP targets come first.

The next few subsections point out and discuss essential performance metrics.

Responsiveness

Responsiveness means how fast the system provides outputs (possibly multiple ones) in response to user inputs. For example, when a user taps the screen, they expect the pixels to change in a certain way. For this interaction, the responsiveness metric is the time elapsed between the tap and the pixel change.

Responsiveness sometimes involves multiple stages of feedback. Application launch is one particularly important case discussed in more detail below.

Responsiveness is important because people get frustrated and angry when they're ignored. Your app is ignoring the user every second that it fails to respond to the user's input.

Frame rate

Frame rate is the rate at which the system changes pixels displayed to the user. This is a familiar concept: everyone prefers, say, games that display 60 frames per second over ones that display 10 frames per second, even if they can't explain why.

Frame rate is important as a "quality of service" metric. Computer displays are designed to "fool" user's eyes, by delivering photons to them that mimic reality. For example, paper covered with printed text reflects photons to the user's eyes in some pattern. By manipulating pixels, a reader app emits photons in a similar pattern to "fool" the user's eyes.

As your brain infers, motion is not jerky and discrete, but rather "updates" smoothly and continuously. (Strobe lights are fun because they turn that upside down, starving your brain of inputs to create the illusion of discrete reality). On a computer display, a higher frame rate makes for a more faithful imitation of reality.

Note: Humans usually cannot perceive differences in frame rate above 60Hz. That's why most modern electronic displays are designed to refresh at that rate. A television probably looks choppy and unrealistic to a hummingbird, for example.

Memory usage

Memory usage is another key metric. Unlike responsiveness and frame rate, users don't directly perceive memory usage, but memory usage closely approximates "user state". An ideal system would maintain 100% of user state at all times: all applications in the system would run simultaneously, and all applications would retain the state created by the user the last time the user interacted with the application (application state is stored in computer memory, which is why the approximation is close).

From this comes an important but counter-intuitive corollary: a well-designed system does not maximize the amount of free memory. Memory is a resource, and free memory is an unused resource. Rather, a well-designed system has been optimized to use as much memory as possible to maintain user state, while meeting other UPP goals.

That doesn't mean the system should waste memory. When a system uses more memory than necessary to maintain some particular user state, the system is wasting a resource it could use to retain some other user state. In practice, no system can maintain all user states. Intelligently allocating memory to user state is an important concern that we go over in more detail below.

Power usage

The final metric discussed here is power usage . Like memory usage, users perceive power usage only indirectly, by how long their devices can maintain all other UPP goals. In service of meeting UPP goals, the system must use only the minimum power required.

The remainder of this document will discuss performance in terms of these metrics.

Platform performance optimizations

This section provides a brief overview of how Firefox/Gecko contributes to performance generally, below the level of all applications. From a developer's or user's perspective, this answers the question, "What does the platform do for you?"

Web technologies

The Web platform provides many tools, some better suited for particular jobs than others. All application logic is written in JavaScript. To display graphics, developers can use HTML or CSS (i.e. high-level declarative languages), or use low-level imperative interfaces offered by the <canvas > element (which includes WebGL ). Somewhere "in between" HTML/CSS and Canvas is SVG , which offers some benefits of both.

HTML and CSS greatly increase productivity, sometimes at the expense of frame rate or pixel-level control over rendering. Text and images reflow automatically, UI elements automatically receive the system theme, and the system provides "built-in" support for some use cases developers may not think of initially, like different-resolution displays or right-to-left languages.

The canvas element offers a pixel buffer directly for developers to draw on. This gives developers pixel-level control over rendering and precise control of frame rate, but now the developers need to deal with multiple resolutions and orientations, right-to-left languages, and so forth. Developers draw to canvases using either a familiar 2D drawing API, or WebGL, a "close to the metal" binding that mostly follows OpenGL ES 2.0.

Gecko rendering

The Gecko JavaScript engine supports just-in-time (JIT) compilation. This enables application logic to perform comparably to other virtual machines — such as Java virtual machines — and in some cases even close to "native code".

The graphics pipeline in Gecko that underpins HTML, CSS, and Canvas is optimized in several ways. The HTML/CSS layout and graphics code in Gecko reduces invalidation and repainting for common cases like scrolling; developers get this support "for free". Pixel buffers painted by both Gecko "automatically" and applications to canvas "manually" minimize copies when being drawn to the display framebuffer. This is done by avoiding intermediate surfaces where they would create overhead (such as per-application "back buffers" in many other operating systems), and by using special memory for graphics buffers that can be directly accessed by the compositor hardware. Complex scenes are rendered using the device's GPU for maximum performance. To improve power usage, simple scenes are rendered using special dedicated composition hardware, while the GPU idles or turns off.

Fully static content is the exception rather than the rule for rich applications. Rich applications use dynamic content with animation and transition effects. Transitions and animations are particularly important to applications: developers can use CSS to declare complicated behavior with a simple, high-level syntax. In turn, Gecko's graphics pipeline is highly optimized to render common animations efficiently. Common-case animations are "offloaded" to the system compositor, which can render them in a performant, power-efficient fashion.

An app's startup performance matters just as much as its runtime performance. Gecko is optimized to load a wide variety of content efficiently: the entire Web! Many years of improvements targeting this content, like parallel HTML parsing, intelligent scheduling of reflows and image decoding, clever layout algorithms, etc., translate just as well to improving Web applications on Firefox.

Application performance

This section is intended for developers asking the question: "How can I make my app fast"?

Startup performance

Application startup is punctuated by three user-perceived events, generally speaking:

  • The first is the application first paint — the point at which sufficient application resources have been loaded to paint an initial frame
  • The second is when the application becomes interactive — for example, users are able to tap a button and the application responds
  • The final event is full load — for example when all the user's albums have been listed in a music player

The key to fast startup is to keep two things in mind: UPP is all that matters, and there's a "critical path" to each user-perceived event above. The critical path is exactly and only the code that must run to produce the event.

For example, to paint an application's first frame that comprises visually some HTML and CSS to style that HTML:

  1. The HTML must be parsed
  2. The DOM for that HTML must be constructed
  3. Resources like images in that part of the DOM have to be loaded and decoded
  4. The CSS styles must be applied to that DOM
  5. The styled document has to be reflowed

Nowhere in that list is "load the JS file needed for an uncommon menu"; "fetch and decode the image for the High Scores list", etc. Those work items are not on the critical path to painting the first frame.

It seems obvious, but to reach a user-perceived startup event more quickly, the main "trick" is run only the code on the critical path. Shorten the critical path by simplifying the scene.

The Web platform is highly dynamic. JavaScript is a dynamically-typed language, and the Web platform allows loading code, HTML, CSS, images, and other resources dynamically. These features can be used to defer work that's off the critical path by loading unnecessary content "lazily" some time after startup.

Another problem that can delay startup is idle time, caused by waiting for responses to requests (like database loads). To avoid this problem, applications should issue requests as early as possible in startup (this is called "front-loading"). Then when the data is needed later, hopefully it's already available and the application doesn't have to wait.

Note: For much more information on improving startup performance, read Optimizing startup performance .

On the same note, notice that locally-cached, static resources can be loaded much faster than dynamic data fetched over high-latency, low-bandwidth mobile networks. Network requests should never be on the critical path to early application startup. Local caching/offline apps can be achieved via Service Workers . See Offline and background operation for a guide about using service workers for offline and background sync capabilities.

Frame rate

The first important thing for high frame rate is to choose the right tool. Use HTML and CSS to implement content that's mostly static, scrolled, and infrequently animated. Use Canvas to implement highly dynamic content, like games that need tight control over rendering and don't need theming.

For content drawn using Canvas, it's up to the developer to hit frame rate targets: they have direct control over what's drawn.

For HTML and CSS content, the path to high frame rate is to use the right primitives. Firefox is highly optimized to scroll arbitrary content; this is usually not a concern. But often trading some generality and quality for speed, such as using a static rendering instead of a CSS radial gradient, can push scrolling frame rate over a target. CSS media queries allow these compromises to be restricted only to devices that need them.

Many applications use transitions or animations through "pages", or "panels". For example, the user taps a "Settings" button to transition into an application configuration screen, or a settings menu "pops up". Firefox is highly optimized to transition and animate scenes that:

  • use pages/panels approximately the size of the device screen or smaller
  • transition/animate the CSS transform and opacity properties

Transitions and animations that adhere to these guidelines can be offloaded to the system compositor and run maximally efficiently.

Memory and power usage

Improving memory and power usage is a similar problem to speeding up startup: don't do unneeded work or lazily load uncommonly-used UI resources. Do use efficient data structures and ensure resources like images are optimized well.

Modern CPUs can enter a lower-power mode when mostly idle. Applications that constantly fire timers or keep unnecessary animations running prevent CPUs from entering low-power mode. Power-efficient applications shouldn't do that.

When applications are sent to the background, a visibilitychange event is fired on their documents. This event is a developer's friend; applications should listen for it.

Specific coding tips for application performance

The following practical tips will help improve one or more of the Application performance factors discussed above.

Use CSS animations and transitions

Instead of using some library's animate() function, which probably currently uses many badly performing technologies ( setTimeout() or top /left positioning, for example) use CSS animations . In many cases, you can actually use CSS Transitions to get the job done. This works well because the browser is designed to optimize these effects and use the GPU to handle them smoothly with minimal impact on processor performance. Another benefit is that you can define these effects in CSS along with the rest of your app's look-and-feel, using a standardized syntax.

CSS animations give you very granular control over your effects using keyframes , and you can even watch events fired during the animation process in order to handle other tasks that need to be performed at set points in the animation process. You can easily trigger these animations with the :hover , :focus , or :target , or by dynamically adding and removing classes on parent elements.

If you want to create animations on the fly or modify them in JavaScript , James Long has written a simple library for that called CSS-animations.js .

Use CSS transforms

Instead of tweaking absolute positioning and fiddling with all that math yourself, use the transform CSS property to adjust the position, scale, and so forth of your content. Alternatively, you can use the individual transformation properties of translate , scale , and rotate . The reason is, once again, hardware acceleration. The browser can do these tasks on your GPU, letting the CPU handle other things.

In addition, transforms give you capabilities you might not otherwise have. Not only can you translate elements in 2D space, but you can transform in three dimensions, skew and rotate, and so forth. Paul Irish has an in-depth analysis of the benefits of translate() (2012) from a performance point of view. In general, however, you have the same benefits you get from using CSS animations: you use the right tool for the job and leave the optimization to the browser. You also use an easily extensible way of positioning elements — something that needs a lot of extra code if you simulate translation with top and left positioning. Another bonus is that this is just like working in a canvas element.

Note: You may need to attach a translateZ(0.1) transform if you wish to get hardware acceleration on your CSS animations, depending on platform. As noted above, this can improve performance. When overused, it can have memory consumption issues. What you do in this regard is up to you — do some testing and find out what's best for your particular app.

Use requestAnimationFrame() instead of setInterval()

Calls to setInterval() run code at a presumed frame rate that may or may not be possible under current circumstances. It tells the browser to render results even while the browser isn't actually drawing; that is, while the video hardware hasn't reached the next display cycle. This wastes processor time and can even lead to reduced battery life on the user's device.

Instead, you should try to use window.requestAnimationFrame() . This waits until the browser is actually ready to start building the next frame of your animation, and won't bother if the hardware isn't going to actually draw anything. Another benefit to this API is that animations won't run while your app isn't visible on the screen (such as if it's in the background and some other task is operating). This will save battery life and prevent users from cursing your name into the night sky.

Make events immediate

As old-school, accessibility-aware Web developers we love click events since they also support keyboard input. On mobile devices, these are too slow. You should use touchstart and touchend instead. The reason is that these don't have a delay that makes the interaction with the app appear sluggish. If you test for touch support first, you don't sacrifice accessibility, either. For example, the Financial Times uses a library called fastclick for that purpose, which is available for you to use.

Keep your interface simple

One big performance issue we found in HTML apps was that moving lots of DOM elements around makes everything sluggish — especially when they feature lots of gradients and drop shadows. It helps a lot to simplify your look-and-feel and move a proxy element around when you drag and drop.

When, for example, you have a long list of elements (let's say tweets), don't move them all. Instead, keep in your DOM tree only the ones that are visible and a few on either side of the currently visible set of tweets. Hide or remove the rest. Keeping the data in a JavaScript object instead of accessing the DOM can vastly improve your app's performance. Think of the display as a presentation of your data rather than the data itself. That doesn't mean you can't use straight HTML as the source; just read it once and then scroll 10 elements, changing the content of the first and last accordingly to your position in the results list, instead of moving 100 elements that aren't visible. The same trick applies in games to sprites: if they aren't currently on the screen, there is no need to poll them. Instead, re-use elements that scroll off-screen as new ones coming in.

General application performance analysis

Firefox, Chrome, and other browsers include built-in tools that can help you diagnose slow page rendering. In particular, Firefox's Network Monitor will display a precise timeline of when each network request on your page happens, how large it is, and how long it takes.

The Firefox network monitor showing get requests, multiple files, and different times taken to load each resource on a graph.

If your page contains JavaScript code that is taking a long time to run, the JavaScript profiler will pinpoint the slowest lines of code:

The Firefox JavaScript profiler showing a completed profile 1.

The Built-in Gecko Profiler is a very useful tool that provides even more detailed information about which parts of the browser code are running slowly while the profiler runs. This is a bit more complex to use, but provides a lot of useful details.

A built-in Gecko profiler windows showing a lot of network information.

Note: You can use these tools with the Android browser by running Firefox and enabling about:debugging .

In particular, making dozens or hundreds of network requests takes longer in mobile browsers. Rendering large images and CSS gradients can also take longer. Downloading large files can take longer, even over a fast network, because mobile hardware is sometimes too slow to take advantage of all the available bandwidth. For useful general tips on mobile Web performance, have a look at Maximiliano Firtman's Mobile Web High Performance talk.

Test cases and submitting bugs

If the Firefox and Chrome developer tools don't help you find a problem, or if they seem to indicate that the Web browser has caused the problem, try to provide a reduced test case that maximally isolates the problem. That often helps in diagnosing problems.

See if you can reproduce the problem by saving and loading a static copy of an HTML page (including any images/stylesheets/scripts it embeds). If so, edit the static files to remove any private information, then send them to others for help (submit a Bugzilla report, for example, or host it on a server and share the URL). You should also share any profiling information you've collected using the tools listed above.

Populating the page: how browsers work

Users want web experiences with content that is fast to load and smooth to interact with. Therefore, a developer should strive to achieve these two goals.

To understand how to improve performance and perceived performance, it helps to understand how the browser works.

Overview

Fast sites provide better user experiences. Users want and expect web experiences with content that is fast to load and smooth to interact with.

Two major issues in web performance are issues having to do with latency and issues having to do with the fact that for the most part, browsers are single-threaded.

Latency is the biggest threat to our ability to ensure a fast-loading page. It is the developers' goal to make the site load as fast as possible — or at least appear to load super fast — so the user gets the requested information as quickly as possible. Network latency is the time it takes to transmit bytes over the air to computers. Web performance is what we have to do to make the page load as quickly as possible.

For the most part, browsers are considered single-threaded. That is, they execute a task from beginning to end before taking up another task. For smooth interactions, the developer's goal is to ensure performant site interactions, from smooth scrolling to being responsive to touch. Render time is key, ensuring the main thread can complete all the work we throw at it and still always be available to handle user interactions. Web performance can be improved by understanding the single-threaded nature of the browser and minimizing the main thread's responsibilities, where possible and appropriate, to ensure rendering is smooth and responses to interactions are immediate.

Navigation is the first step in loading a web page. It occurs whenever a user requests a page by entering a URL into the address bar, clicking a link, submitting a form, as well as other actions.

One of the goals of web performance is to minimize the amount of time navigation takes to complete. In ideal conditions, this usually doesn't take too long, but latency and bandwidth are foes that can cause delays.

DNS lookup

The first step of navigating to a web page is finding where the assets for that page are located. If you navigate to https://example.com , the HTML page is located on the server with IP address of 93.184.216.34 . If you've never visited this site, a DNS lookup must happen.

Your browser requests a DNS lookup, which is eventually fielded by a name server, which in turn responds with an IP address. After this initial request, the IP will likely be cached for a time, which speeds up subsequent requests by retrieving the IP address from the cache instead of contacting a name server again.

DNS lookups usually only need to be done once per hostname for a page load. However, DNS lookups must be done for each unique hostname the requested page references. If your fonts, images, scripts, ads, and metrics all have different hostnames, a DNS lookup will have to be made for each one.

Mobile requests go first to the cell tower, then to a central phone company computer before being sent to the internet

This can be problematic for performance, particularly on mobile networks. When a user is on a mobile network, each DNS lookup has to go from the phone to the cell tower to reach an authoritative DNS server. The distance between a phone, a cell tower, and the name server can add significant latency.

TCP handshake

Once the IP address is known, the browser sets up a connection to the server via a TCP three-way handshake . This mechanism is designed so that two entities attempting to communicate — in this case the browser and web server — can negotiate the parameters of the network TCP socket connection before transmitting data, often over HTTPS .

TCP's three-way handshaking technique is often referred to as "SYN-SYN-ACK" — or more accurately SYN, SYN-ACK, ACK — because there are three messages transmitted by TCP to negotiate and start a TCP session between two computers. Yes, this means three more messages back and forth between each server, and the request has yet to be made.

TLS negotiation

For secure connections established over HTTPS, another "handshake" is required. This handshake, or rather the TLS negotiation, determines which cipher will be used to encrypt the communication, verifies the server, and establishes that a secure connection is in place before beginning the actual transfer of data. This requires five more round trips to the server before the request for content is actually sent.

The DNS lookup, the TCP handshake, and 5 steps of the TLS handshake including clienthello, serverhello and certificate, clientkey and finished for both server and client.

While making the connection secure adds time to the page load, a secure connection is worth the latency expense, as the data transmitted between the browser and the web server cannot be decrypted by a third party.

After the eight round trips to the server, the browser is finally able to make the request.

Response

Once we have an established connection to a web server, the browser sends an initial HTTP GET request on behalf of the user, which for websites is most often an HTML file. Once the server receives the request, it will reply with relevant response headers and the contents of the HTML.

html
                                        
                                            
                                                <!
                                                doctype
                                                html
                                                >
                                            
                                            
                                                
                                                    <
                                                    html
                                                
                                                lang
                                                
                                                    =
                                                    "
                                                    en-US"
                                                
                                                >
                                            
                                            
                                                
                                                    <
                                                    head
                                                
                                                >
                                            
                                            
                                                
                                                    <
                                                    meta
                                                
                                                charset
                                                
                                                    =
                                                    "
                                                    UTF-8"
                                                
                                                />
                                            
                                            
                                                
                                                    <
                                                    title
                                                
                                                >
                                            
                                            My simple page
                                            
                                                
                                                    </
                                                    title
                                                
                                                >
                                            
                                            
                                                
                                                    <
                                                    link
                                                
                                                rel
                                                
                                                    =
                                                    "
                                                    stylesheet"
                                                
                                                href
                                                
                                                    =
                                                    "
                                                    styles.css"
                                                
                                                />
                                            
                                            
                                                
                                                    <
                                                    script
                                                
                                                src
                                                
                                                    =
                                                    "
                                                    myscript.js"
                                                
                                                >
                                            
                                            
                                            
                                                
                                                    </
                                                    script
                                                
                                                >
                                            
                                            
                                                
                                                    </
                                                    head
                                                
                                                >
                                            
                                            
                                                
                                                    <
                                                    body
                                                
                                                >
                                            
                                            
                                                
                                                    <
                                                    h1
                                                
                                                class
                                                
                                                    =
                                                    "
                                                    heading"
                                                
                                                >
                                            
                                            My Page
                                            
                                                
                                                    </
                                                    h1
                                                
                                                >
                                            
                                            
                                                
                                                    <
                                                    p
                                                
                                                >
                                            
                                            A paragraph with a 
                                            
                                                
                                                    <
                                                    a
                                                
                                                href
                                                
                                                    =
                                                    "
                                                    https://example.com/about"
                                                
                                                >
                                            
                                            link
                                            
                                                
                                                    </
                                                    a
                                                
                                                >
                                            
                                            
                                                
                                                    </
                                                    p
                                                
                                                >
                                            
                                            
                                                
                                                    <
                                                    div
                                                
                                                >
                                            
                                            
                                                
                                                    <
                                                    img
                                                
                                                src
                                                
                                                    =
                                                    "
                                                    myimage.jpg"
                                                
                                                alt
                                                
                                                    =
                                                    "
                                                    image description"
                                                
                                                />
                                            
                                            
                                                
                                                    </
                                                    div
                                                
                                                >
                                            
                                            
                                                
                                                    <
                                                    script
                                                
                                                src
                                                
                                                    =
                                                    "
                                                    anotherscript.js"
                                                
                                                >
                                            
                                            
                                            
                                                
                                                    </
                                                    script
                                                
                                                >
                                            
                                            
                                                
                                                    </
                                                    body
                                                
                                                >
                                            
                                            
                                                
                                                    </
                                                    html
                                                
                                                >
                                            
                                        
                                    

This response for this initial request contains the first byte of data received. Time to First Byte (TTFB) is the time between when the user made the request — say by clicking on a link — and the receipt of this first packet of HTML. The first chunk of content is usually 14KB of data.

In our example above, the request is definitely less than 14KB, but the linked resources aren't requested until the browser encounters the links during parsing, described below.

Congestion control / TCP slow start

TCP packets are split into segments during transmission. Because TCP guarantees the sequence of packets, the server must receive an acknowledgment from the client in the form of an ACK packet after sending a certain number of segments.

If the server waits for an ACK after each segment, that will result in frequent ACKs from the client and may increase transmission time, even in the case of a low-load network.

On the other hand, sending too many segments at once can lead to the problem that in a busy network the client will not be able to receive the segments and will just keep responding with ACKs for a long time, and the server will have to keep re-sending the segments.

In order to balance the number of transmitted segments, the TCP slow start algorithm is used to gradually increase the amount of transmitted data until the maximum network bandwidth can be determined, and to reduce the amount of transmitted data in case of high network load.

The number of segments to be transmitted is controlled by the value of the congestion window (CWND), which can be initialized to 1, 2, 4, or 10 MSS (MSS is 1500 bytes over the Ethernet protocol). That value is the number of bytes to send, upon receipt of which the client must send an ACK.

If an ACK is received, then the CWND value will be doubled, and so the server will be able to send more segments the next time. If instead no ACK is received, then the CWND value will be halved. That mechanism thus achieves a balance between sending too many segments, and sending too few.

Parsing

Once the browser receives the first chunk of data, it can begin parsing the information received. Parsing is the step the browser takes to turn the data it receives over the network into the DOM and CSSOM , which is used by the renderer to paint a page to the screen.

The DOM is the internal representation of the markup for the browser. The DOM is also exposed and can be manipulated through various APIs in JavaScript.

Even if the requested page's HTML is larger than the initial 14KB packet, the browser will begin parsing and attempting to render an experience based on the data it has. This is why it's important for web performance optimization to include everything the browser needs to start rendering a page, or at least a template of the page — the CSS and HTML needed for the first render — in the first 14KB. But before anything is rendered to the screen, the HTML, CSS, and JavaScript have to be parsed.

Building the DOM tree

We describe five steps in the critical rendering path .

The first step is processing the HTML markup and building the DOM tree. HTML parsing involves tokenization and tree construction. HTML tokens include start and end tags, as well as attribute names and values. If the document is well-formed, parsing it is straightforward and faster. The parser parses tokenized input into the document, building up the document tree.

The DOM tree describes the content of the document. The <html > element is the first element and root node of the document tree. The tree reflects the relationships and hierarchies between different elements. Elements nested within other elements are child nodes. The greater the number of DOM nodes, the longer it takes to construct the DOM tree.

The DOM tree for our sample code, showing all the nodes, including text nodes.

When the parser finds non-blocking resources, such as an image, the browser will request those resources and continue parsing. Parsing can continue when a CSS file is encountered, but <script > elements — particularly those without an async or defer attribute — block rendering, and pause the parsing of HTML. Though the browser's preload scanner hastens this process, excessive scripts can still be a significant bottleneck.

Preload scanner

While the browser builds the DOM tree, this process occupies the main thread. As this happens, the preload scanner will parse through the content available and request high-priority resources like CSS, JavaScript, and web fonts. Thanks to the preload scanner, we don't have to wait until the parser finds a reference to an external resource to request it. It will retrieve resources in the background so that by the time the main HTML parser reaches the requested assets, they may already be in flight or have been downloaded. The optimizations the preload scanner provides reduce blockages.

html
                                        
                                            
                                                
                                                    <
                                                    link
                                                
                                                rel
                                                
                                                    =
                                                    "
                                                    stylesheet"
                                                
                                                href
                                                
                                                    =
                                                    "
                                                    styles.css"
                                                
                                                />
                                            
                                            
                                                
                                                    <
                                                    script
                                                
                                                src
                                                
                                                    =
                                                    "
                                                    myscript.js"
                                                
                                                async
                                                >
                                            
                                            
                                            
                                                
                                                    </
                                                    script
                                                
                                                >
                                            
                                            
                                                
                                                    <
                                                    img
                                                
                                                src
                                                
                                                    =
                                                    "
                                                    myimage.jpg"
                                                
                                                alt
                                                
                                                    =
                                                    "
                                                    image description"
                                                
                                                />
                                            
                                            
                                                
                                                    <
                                                    script
                                                
                                                src
                                                
                                                    =
                                                    "
                                                    anotherscript.js"
                                                
                                                async
                                                >
                                            
                                            
                                            
                                                
                                                    </
                                                    script
                                                
                                                >
                                            
                                        
                                    

In this example, while the main thread is parsing the HTML and CSS, the preload scanner will find the scripts and image, and start downloading them as well. To ensure the script doesn't block the process, add the async attribute, or the defer attribute if JavaScript parsing and execution order is important.

Waiting to obtain CSS doesn't block HTML parsing or downloading, but it does block JavaScript because JavaScript is often used to query CSS properties' impact on elements.

Building the CSSOM tree

The second step in the critical rendering path is processing CSS and building the CSSOM tree. The CSS object model is similar to the DOM. The DOM and CSSOM are both trees. They are independent data structures. The browser converts the CSS rules into a map of styles it can understand and work with. The browser goes through each rule set in the CSS, creating a tree of nodes with parent, child, and sibling relationships based on the CSS selectors.

As with HTML, the browser needs to convert the received CSS rules into something it can work with. Hence, it repeats the HTML-to-object process, but for the CSS.

The CSSOM tree includes styles from the user agent style sheet. The browser begins with the most general rule applicable to a node and recursively refines the computed styles by applying more specific rules. In other words, it cascades the property values.

Building the CSSOM is very, very fast and is not displayed in a unique color in current developer tools. Rather, the "Recalculate Style" in developer tools shows the total time it takes to parse CSS, construct the CSSOM tree, and recursively calculate computed styles. In terms of web performance optimization, there are lower hanging fruit, as the total time to create the CSSOM is generally less than the time it takes for one DNS lookup.

Other processes

JavaScript compilation

While the CSS is being parsed and the CSSOM created, other assets, including JavaScript files, are downloading (thanks to the preload scanner). JavaScript is parsed, compiled, and interpreted. The scripts are parsed into abstract syntax trees. Some browser engines take the abstract syntax trees and pass them into a compiler, outputting bytecode. This is known as JavaScript compilation. Most of the code is interpreted on the main thread, but there are exceptions such as code run in web workers .

Building the accessibility tree

The browser also builds an accessibility tree that assistive devices use to parse and interpret content. The accessibility object model (AOM) is like a semantic version of the DOM. The browser updates the accessibility tree when the DOM is updated. The accessibility tree is not modifiable by assistive technologies themselves.

Until the AOM is built, the content is not accessible to screen readers .

Render

Rendering steps include style, layout, paint, and in some cases compositing. The CSSOM and DOM trees created in the parsing step are combined into a render tree which is then used to compute the layout of every visible element, which is then painted to the screen. In some cases, content can be promoted to its own layer and composited, improving performance by painting portions of the screen on the GPU instead of the CPU, freeing up the main thread.

Style

The third step in the critical rendering path is combining the DOM and CSSOM into a render tree. The computed style tree, or render tree, construction starts with the root of the DOM tree, traversing each visible node.

Elements that aren't going to be displayed, like the <head > element and its children and any nodes with display: none , such as the script { display: none; } you will find in user agent stylesheets, are not included in the render tree as they will not appear in the rendered output. Nodes with visibility: hidden applied are included in the render tree, as they do take up space. As we have not given any directives to override the user agent default, the script node in our code example above will not be included in the render tree.

Each visible node has its CSSOM rules applied to it. The render tree holds all the visible nodes with content and computed styles — matching up all the relevant styles to every visible node in the DOM tree, and determining, based on the CSS cascade , what the computed styles are for each node.

Layout

The fourth step in the critical rendering path is running layout on the render tree to compute the geometry of each node. Layout is the process by which the dimensions and location of all the nodes in the render tree are determined, plus the determination of the size and position of each object on the page. Reflow is any subsequent size and position determination of any part of the page or the entire document.

Once the render tree is built, layout commences. The render tree identified which nodes are displayed (even if invisible) along with their computed styles, but not the dimensions or location of each node. To determine the exact size and position of each object, the browser starts at the root of the render tree and traverses it.

On the web page, almost everything is a box. Different devices and different desktop preferences mean an unlimited number of differing viewport sizes. In this phase, taking the viewport size into consideration, the browser determines what the sizes of all the different boxes are going to be on the screen. Taking the size of the viewport as its base, layout generally starts with the body, laying out the sizes of all the body's descendants, with each element's box model properties, providing placeholder space for replaced elements it doesn't know the dimensions of, such as our image.

The first time the size and position of each node is determined is called layout . Subsequent recalculations of are called reflows . In our example, suppose the initial layout occurs before the image is returned. Since we didn't declare the dimensions of our image, there will be a reflow once the image dimensions are known.

Paint

The last step in the critical rendering path is painting the individual nodes to the screen, the first occurrence of which is called the first meaningful paint . In the painting or rasterization phase, the browser converts each box calculated in the layout phase to actual pixels on the screen. Painting involves drawing every visual part of an element to the screen, including text, colors, borders, shadows, and replaced elements like buttons and images. The browser needs to do this super quickly.

To ensure smooth scrolling and animation, everything occupying the main thread, including calculating styles, along with reflow and paint, must take the browser less than 16.67ms to accomplish. At 2048 x 1536, the iPad has over 3,145,000 pixels to be painted to the screen. That is a lot of pixels that have to be painted very quickly. To ensure repainting can be done even faster than the initial paint, the drawing to the screen is generally broken down into several layers. If this occurs, then compositing is necessary.

Painting can break the elements in the layout tree into layers. Promoting content into layers on the GPU (instead of the main thread on the CPU) improves paint and repaint performance. There are specific properties and elements that instantiate a layer, including <video > and <canvas > , and any element which has the CSS properties of opacity , a 3D transform , will-change , and a few others. These nodes will be painted onto their own layer, along with their descendants, unless a descendant necessitates its own layer for one (or more) of the above reasons.

Layers do improve performance but are expensive when it comes to memory management, so should not be overused as part of web performance optimization strategies.

Compositing

When sections of the document are drawn in different layers, overlapping each other, compositing is necessary to ensure they are drawn to the screen in the right order and the content is rendered correctly.

As the page continues to load assets, reflows can happen (recall our example image that arrived late). A reflow sparks a repaint and a re-composite. Had we defined the dimensions of our image, no reflow would have been necessary, and only the layer that needed to be repainted would be repainted, and composited if necessary. But we didn't include the image dimensions! When the image is obtained from the server, the rendering process goes back to the layout steps and restarts from there.

Interactivity

Once the main thread is done painting the page, you would think we would be "all set." That isn't necessarily the case. If the load includes JavaScript, that was correctly deferred, and only executed after the onload event fires, the main thread might be busy, and not available for scrolling, touch, and other interactions.

Time to Interactive (TTI) is the measurement of how long it took from that first request which led to the DNS lookup and TCP connection to when the page is interactive — interactive being the point in time after the First Contentful Paint when the page responds to user interactions within 50ms. If the main thread is occupied parsing, compiling, and executing JavaScript, it is not available and therefore not able to respond to user interactions in a timely (less than 50ms) fashion.

In our example, maybe the image loaded quickly, but perhaps the anotherscript.js file was 2MB and our user's network connection was slow. In this case, the user would see the page super quickly, but wouldn't be able to scroll without jank until the script was downloaded, parsed, and executed. That is not a good user experience. Avoid occupying the main thread, as demonstrated in this WebPageTest example:

The main thread is occupied by the downloading, parsing and execution of a JavaScript file - over a fast connection

In this example, JavaScript execution took over 1.5 seconds, and the main thread was fully occupied that entire time, unresponsive to click events or screen taps.

See also

Recommended Web Performance Timings: How long is too long?

There are no clear set rules as to what constitutes a slow pace when loading pages, but there are specific guidelines for indicating content will load (1 second), idling (50ms), animating (16.7ms) and responding to user input (50 to 200ms).

Load goal

The 'Under a second' is often touted as optimal for load, but what does that mean? A second should be considered a rule in the maximum amount of time to indicate to a user that the request for new content was made and will load, such as the browser displaying the page title and the background color of the page displaying.

The first asset retrieved from a request is usually an HTML document, which then makes calls for additional assets. As noted in the description of the critical rendering path , when received, browsers immediately start processing the HTML, rendering the content as it is received rather than waiting for additional assets to load.

Yes, one second for loading is a goal, but it's something few sites achieve. Expectations differ. A 'hello world' on the corporate network would be expected to load in milliseconds, but a user downloading a cat video on a five-year-old device over an edge network in northern Siberia would likely find a 20-second download speedy. If you wait three or four seconds without communicating to the user that a load is happening and showing some progress, the typical site will lose potential visitors, and those visitors will take a long time to come back if they ever do.

In optimizing for performance, do set an ambitious first load goal, such as 5 seconds over the mobile 3G network and 1.5 seconds on an office T1 line, with even more ambitious page load goals for subsequent page loads, leveraging service workers and caching. There are different suggested times for initially loading the page versus loading additional assets, responding to user interaction, and ensuring smooth animations:

Idling goal

Browsers are single threaded (though background threads are supported for web workers). This means that user interaction, painting, and script execution are all on the same thread. If the thread is busy doing complex JavaScript execution, the main thread will not be available to react to user input, such as pressing a button. For this reason, script execution should be limited in scope, divided into chunks of code that can be executed in 50ms or less. This makes the thread available for user interactions.

Animation goal

For scrolling and other animations to look smooth and feel responsive, the content repaints should occur at 60 frames per second (60fps), which is once every 16.7ms. The 16.7 milliseconds includes scripting, reflow, and repaint. Realize a document takes about 6ms to render a frame, leaving about 10ms for the rest. Anything less than 60fps, especially an un-even or changing frame rate, will appear janky.

Responsiveness goal

When the user interacts with content, it is important to provide feedback and acknowledge the user's response or interaction and to do so within 100ms, preferably within 50ms. 50ms seconds feels immediate. The acknowledgment of user interaction should often feel immediate, such as a hover or button press, but that doesn't mean the completed response should be instantaneous. While a slower than 100ms reaction may create a disconnect between the user interaction and the response, a 100 to 200ms transition for a response may help the user notice the response their interaction initiated, such as a menu opening. If a response takes longer than 100ms to complete, provide some form of feedback to inform the user the interaction has occurred.

Speculative loading

Speculative loading refers to the practice of performing navigation actions (such as DNS fetching, fetching resources, or rendering documents) before the associated pages are actually visited, based on predictions as to what pages the user is most likely to visit next.

These predictions can be supplied by developers (for example, lists of the most popular destinations on their site) or determined by browser heuristics (for example based on popular sites in the user's history). When used successfully, such technologies can significantly improve performance by making pages available more quickly, or in some cases, instantly.

This page reviews available speculative loading technologies and when they can and should be used to improve performance.

Speculative loading mechanisms

There are several mechanisms for speculative loading:

  • Prefetching involves fetching some or all of the resources required to render a document (or part of a document) before they are needed, so that when the time comes to render it, rendering can be achieved much more quickly.
  • Prerendering goes a step further, and actually renders the content ready to be shown when required. Depending on how this is done, this can result in an instant navigation from old page to new page.
  • Preconnecting involves speeding up future loads from a given origin by preemptively performing part or all of the connection handshake (i.e. DNS + TCP + TLS).

Note: The above descriptions are high-level and general. Exactly what browsers will do to achieve prefetching and prerendering depends on the features used. More exact feature descriptions are provided in the Speculative loading features section below.

How is speculative loading achieved?

Speculative loading is achieved in two main ways.

First, some browsers will automatically prerender pages based on various heuristics to provide automatic performance improvements. Exactly how this is done depends on the browser implementation. Chrome, for example, automatically prerenders pages when matching strings are typed into the address bar — if it has a high confidence that you will visit that page (see Viewing Chrome's address bar predictions for more details). In addition, it may automatically prerender search results pages when search terms are typed into the address bar, when instructed to do so by the search engine. It does this using the same mechanism as the Speculation Rules API .

Second, there are several different platform features that developers can use to provide instructions on what speculative loading they want the browser to perform. These are reviewed in the next section.

Speculative loading features

<link rel="preconnect"> provides a hint to browsers that the user is likely to need resources from the specified resource's origin, and therefore the browser can likely improve performance by preemptively initiating a connection to that origin. Supporting browsers will preemptively perform part or all of the connection handshake (i.e. DNS + TCP + TLS).

For example:

html
                                        
                                            
                                                
                                                    <
                                                    link
                                                
                                                rel
                                                
                                                    =
                                                    "
                                                    preconnect"
                                                
                                                href
                                                
                                                    =
                                                    "
                                                    https://example.com"
                                                
                                                />
                                            
                                        
                                    

<link rel="preconnect"> is widely supported across browsers, and will provide a benefit to any future cross-origin HTTP request, navigation or subresource. It has no benefit on same-origin requests because the connection is already open.

If a page needs to make connections to many third-party domains, preconnecting them all can be counterproductive. The <link rel="preconnect"> hint is best used for only the most critical connections. For the others, just use <link rel="dns-prefetch"> to save time on the first step — the DNS lookup.

You can also implement preconnect as an HTTP Link header, for example:

http
                                        
                                            
                                                Link
                                                :
                                                <https://example.com >; rel="preconnect"
                                            
                                        
                                    

<link rel="dns-prefetch"> provides a hint to browsers that the user is likely to need resources from the specified resource's origin, and therefore the browser may be able to improve performance by preemptively performing DNS resolution for that origin. It is identical to <link rel="preconnect"> except that it only handles the DNS part.

Again, browser support is widespread, and it has no benefit on same-origin requests because the connection is already open.

For example:

html
                                        
                                            
                                                
                                                    <
                                                    link
                                                
                                                rel
                                                
                                                    =
                                                    "
                                                    dns-prefetch"
                                                
                                                href
                                                
                                                    =
                                                    "
                                                    https://example.com"
                                                
                                                />
                                            
                                        
                                    

Note: See Using dns-prefetch for more details.

<link rel="preload"> provides a hint to browsers as to what resources are high-priority on the current page , so it can start downloading them early when it sees the <link > element(s) in the <head > of the page.

For example:

html
                                        
                                            
                                                
                                                    <
                                                    link
                                                
                                                rel
                                                
                                                    =
                                                    "
                                                    preload"
                                                
                                                href
                                                
                                                    =
                                                    "
                                                    main.js"
                                                
                                                as
                                                
                                                    =
                                                    "
                                                    script"
                                                
                                                />
                                            
                                            <!-- CORS-enabled preload -->
                                            
                                                
                                                    <
                                                    link
                                                
                                                rel
                                                
                                                    =
                                                    "
                                                    preload"
                                                
                                                href
                                                
                                                    =
                                                    "
                                                    https://www.example.com/fonts/cicle_fina-webfont.woff2"
                                                
                                                as
                                                
                                                    =
                                                    "
                                                    font"
                                                
                                                type
                                                
                                                    =
                                                    "
                                                    font/woff2"
                                                
                                                crossorigin
                                                />
                                            
                                        
                                    

The result is kept in a per-document in-memory cache. If you preload something your current page doesn't use as a subresource, it is generally a waste of resources, although the result may populate the HTTP cache if headers allow.

You can also implement preload as an HTTP Link header, for example:

http
                                        
                                            
                                                Link
                                                :
                                                <https://www.example.com/fonts/cicle_fina-webfont.woff2 >; rel="preload"
                                            
                                        
                                    

Browser support for <link rel="preload"> /<link rel="modulepreload"> is widespread in modern browsers.

<link rel="modulepreload"> provides a hint to browsers as to what JavaScript modules are high-priority on the current page , so it can start downloading them early.

For example:

js
                                        
                                            <
                                            link rel=
                                            "modulepreload"
                                            href=
                                            "main.js"
                                            /
                                            >
                                        
                                    

It is a specialized version of <link rel="preload"> for JavaScript modules and works basically the same way. However, there are some differences:

  • The browser knows the resource is a JavaScript module, as the as attribute is not needed, and it can use the correct credentials modes to avoid double-fetching.
  • Rather than just downloading it and storing it in a cache, the browser downloads it, then parses and compiles it directly into the in-memory module map.
  • The browser can also do the same for module dependencies automatically.

<link rel="prefetch"> provides a hint to browsers that the user is likely to need the target resource for future navigations, and therefore the browser can likely improve the user experience by preemptively fetching and caching the resource. <link rel="prefetch"> is used for same-site navigation resources, or for subresources used by same-site pages.

For example:

js
                                        
                                            <
                                            link rel=
                                            "prefetch"
                                            href=
                                            "main.js"
                                            /
                                            >
                                        
                                    

Prefetching can be used to fetch both HTML and sub-resources for a possible next navigation. A common use case is to have a simple website landing page that fetches more "heavy-weight" resources used by the rest of the site.

html
                                        
                                            
                                                
                                                    <
                                                    link
                                                
                                                rel
                                                
                                                    =
                                                    "
                                                    prefetch"
                                                
                                                href
                                                
                                                    =
                                                    "
                                                    /app/style.css"
                                                
                                                />
                                            
                                            
                                                
                                                    <
                                                    link
                                                
                                                rel
                                                
                                                    =
                                                    "
                                                    prefetch"
                                                
                                                href
                                                
                                                    =
                                                    "
                                                    /landing-page"
                                                
                                                />
                                            
                                        
                                    

The result is kept in the HTTP cache on disk. Because of this it is useful for prefetching subresources, even if they are not used by the current page. You could also use it to prefetch the next document the user will likely visit on the site. However, as a result you need to be careful with headers — for example certain Cache-Control headers could block prefetching (for example no-cache or no-store ).

Many browsers now implement some form of cache partitioning , which makes <link rel="prefetch"> useless for resources intended for use by different top-level sites. This includes the main document when navigating cross-site. So, for example, the following prefetch:

html
                                        
                                            
                                                
                                                    <
                                                    link
                                                
                                                rel
                                                
                                                    =
                                                    "
                                                    prefetch"
                                                
                                                href
                                                
                                                    =
                                                    "
                                                    https://news.example/article"
                                                
                                                />
                                            
                                        
                                    

Would not be accessible from https://aggregator.example/ .

Note: <link rel="prefetch"> is functionally equivalent to a fetch() call with a priority: "low" option set on it, except that the former will generally have an even lower priority, and it will have a Sec-Purpose: prefetch header set on the request.

Note: The fetch request for a prefetch operation results in an HTTP Request that includes the HTTP header Sec-Purpose: prefetch . A server might use this header to change the cache timeouts for the resources, or perform other special handling. The request will also include the Sec-Fetch-Dest header with the value set to empty . The Accept header in the request will match the value used for normal navigation requests. This allows the browser to find the matching cached resources following navigation. If a response is returned, it gets cached with the request in the HTTP cache.

Note: This technology was only ever available in Chrome, and is now deprecated. You should use the Speculation Rules API instead, which supercedes this.

<link rel="prerender"> provides a hint to browsers that the user might need the target resource for the next navigation, and therefore the browser can likely improve performance by prerendering the resource. prerender is used for future navigations, same-site only, and as such makes sense for multi-page applications (MPAs), not single-page applications (SPAs).

For example:

html
                                        
                                            
                                                
                                                    <
                                                    link
                                                
                                                rel
                                                
                                                    =
                                                    "
                                                    prerender"
                                                
                                                href
                                                
                                                    =
                                                    "
                                                    /next-page"
                                                
                                                />
                                            
                                        
                                    

It will fetch the referenced document, then fetch any linked resources that are statically findable and fetch them too, storing the result in the HTTP cache on disk with a five-minute timeout. The exception is subresources loaded via JavaScript — it does not find these. It has other problems too — like <link rel="prefetch"> it can also be blocked by Cache-Control headers, and be rendered useless for resources intended for use by different top-level sites by browser cache partitioning .

Speculation Rules API

The Speculation Rules API is used to specify a set of rules that determine what future documents should be prefetched or prerendered by the browser. These rules are provided as JSON structures inside inline <script type="speculationrules"> elements and external text files referenced by the Speculation-Rules response header.

When should you use each feature?

The following table summarizes the features detailed above, and provides guidance on when to use each one.

Speculative loading features Purpose When to use
<link rel="preconnect"> Cross-origin connection warming Use on your most critical cross-origin connections to provide performance improvements when connecting to them.
<link rel="dns-prefetch"> Cross-origin connection warming Use on all of your cross-origin connections to provide small performance improvements when connecting to them.
<link rel="preload"> High-priority loading of current page subresources Use to load high-priority resources faster on the current page, for strategic performance improvements. Don't preload everything, otherwise you won't see the benefit. Also has some other interesting uses — see Preload: What Is It Good For? on Smashing Magazine (2016)
<link rel="modulepreload"> High-priority loading of current page JavaScript modules Use to preload high-priority JavaScript modules for strategic performance improvements.
<link rel="prefetch"> Pre-populating the HTTP cache Use to prefetch same-site future navigation resources or subresources used on those pages. Uses HTTP cache therefore has a number of issues with document prefetches, such as being potentially blocked by Cache-Control headers. Use the Speculation Rules API for document prefetches instead, where it is supported.
<link rel="prerender"> Preparing for the next navigation Deprecated; you are advised not to use this. Use Speculation Rules API prerender instead, where it is supported.
Speculation Rules API prefetch Preparing for the next navigation Use to prefetch same or cross-site future navigation documents. Broad adoption is recommended, where it is supported; check to make sure the pages are safe to prefetch . It doesn't handle subresource prefetches; for that you'll need to use <link rel="prefetch"> .
Speculation Rules API prerender Preparing for the next navigation Use to preload same-origin future navigation resources, for near-instant navigations. Use on high-priority pages, where it is supported; check to make sure the pages are safe to prerender .

See also

Understanding latency

Latency is the time it takes for a packet of data to travel from source to a destination. In terms of performance optimization, it's important to optimize to reduce causes of latency and to test site performance emulating high latency to optimize for users with lousy connections. This article explains what latency is, how it impacts performance, how to measure latency, and how to reduce it.

What is Latency?

Latency is generally considered to be the amount of time it takes from when a request is made by the user to the time it takes for the response to get back to that user. On a first request, for the first 14Kb bytes, latency is longer because it includes a DNS lookup, a TCP handshake , the secure TLS negotiation. Subsequent requests will have less latency because the connection to the server is already set.

Latency describes the amount of delay on a network or Internet connection. Low latency implies that there are no or almost no delays. High latency implies that there are many delays. One of the main aims of improving performance is to reduce latency.

The latency associated with a single asset, especially a basic HTML page, may seem trivial. But websites generally involve multiple requests: the HTML includes requests for multiple CSS, scripts, and media files. The greater the number and size of these requests, the greater the impact of high latency on user experience.

On a connection with low latency, requested resources will appear almost immediately. On a connection with high latency, there will be a discernible delay between the time that a request is sent, and the resources are returned. We can determine the amount of latency by measuring the speed with which the data moves from one network location to another.

Latency can be measured one way, for example, the amount of time it takes to send a request for resources, or the length of the entire round-trip from the browser's request for a resource to the moment when the requested resource arrives at the browser.

Network throttling

To emulate the latency of a low bandwidth network, you can use developer tools and switch to a lower end network connection.

Emulate latency by emulating throttling

In the developer tools, under the network table, you can switch the throttling option to 2G, 3G, etc. Different browser developer tools have different preset options, the characteristics emulated include download speed, upload speed, and minimum latency, or the minimum amount of time it takes to send a packet of data. The approximate values of some presets include:

Selection Download speed Upload speed Minimum latency (ms)
GPRS 50 Kbps 20 Kbps 500
Regular 2G 250 Kbps 50 Kbps 300
Good 2G 450 Kbps 150 Kbps 150
Regular 3G 750 Kbps 250 Kbps 100
Good 3G 1.5 Mbps 750 Kbps 40
Regular 4G/LTE 4 Mbps 3 Mbps 20
DSL 2 Mbps 1 Mbps 5
Wi-Fi 30 Mbps 15 Mbps 2

Network Timings

Also, on the network tab, you can see how long each request took to complete. We can look at how long a 267.5Kb SVG image asset took to download.

The time it took for a large SVG asset to load.

When a request is in a queue, waiting for a network connection it is considered blocked . Blocking happens when there are too many simultaneous connections made to a single server over HTTP. If all connections are in use, the browser can't download more resources until a connection is released, meaning those requests and resources are blocked.

DNS resolution is the time it took to do the DNS lookup. The greater the number of hostnames , the more DNS lookups need to be done.

Connecting is the time it takes for a TCP handshake to complete. Like DNS, the greater the number of server connections needed, the more time is spent creating server connections.

The TLS handshake is how long it took to set up a secure connection. While a TLS handshake does take longer to connect than an insecure connection, the additional time needed for a secure connection is worth it.

Sending is the time taken to send the HTTP request to the server.

Waiting is disk latency, the time it took for the server to complete its response. Disk latency used to be the main area of performance concern. However, server performance has improved as computer memory, or CPU, has improved. Depending on the complexity of what is needed from the server, this can still be an issue.

Receiving is the time it takes to download the asset. The receiving time is determined by a combination of the network capacity and the asset file size. If the image been cached, this would have been nearly instantaneous. Had we throttled, receiving could have been 43 seconds!

Measuring latency

Network latency is the time it takes for a data request to get from the computer making the request, to the computer responding. Including the time it takes for a byte of data to make it from the responding computer back to the requesting computer. It is generally measured as a round trip delay.

Disk latency is the time it takes from the moment a computer, usually a server, receives a request, to the time the computer returns the response.

Using dns-prefetch

DNS-prefetch is an attempt to resolve domain names before resources get requested. This could be a file loaded later or link target a user tries to follow.

Why use dns-prefetch?

When a browser requests a resource from a (third party) server, that cross-origin 's domain name must be resolved to an IP address before the browser can issue the request. This process is known as DNS resolution. While DNS caching can help to reduce this latency, DNS resolution can add significant latency to requests. For websites that open connections to many third parties, this latency can significantly reduce loading performance.

dns-prefetch helps developers mask DNS resolution latency. The HTML <link > element offers this functionality by way of a rel attribute value of dns-prefetch . The cross-origin domain is then specified in the href attribute :

Syntax

html
                                        
                                            
                                                
                                                    <
                                                    link
                                                
                                                rel
                                                
                                                    =
                                                    "
                                                    dns-prefetch"
                                                
                                                href
                                                
                                                    =
                                                    "
                                                    https://fonts.googleapis.com/"
                                                
                                                />
                                            
                                        
                                    

Examples

html
                                        
                                            
                                                
                                                    <
                                                    html
                                                
                                                lang
                                                
                                                    =
                                                    "
                                                    en"
                                                
                                                >
                                            
                                            
                                                
                                                    <
                                                    head
                                                
                                                >
                                            
                                            
                                                
                                                    <
                                                    meta
                                                
                                                charset
                                                
                                                    =
                                                    "
                                                    utf-8"
                                                
                                                />
                                            
                                            
                                                
                                                    <
                                                    meta
                                                
                                                name
                                                
                                                    =
                                                    "
                                                    viewport"
                                                
                                                content
                                                
                                                    =
                                                    "
                                                    width=device-width,initial-scale=1"
                                                
                                                />
                                            
                                            
                                                
                                                    <
                                                    link
                                                
                                                rel
                                                
                                                    =
                                                    "
                                                    dns-prefetch"
                                                
                                                href
                                                
                                                    =
                                                    "
                                                    https://fonts.googleapis.com/"
                                                
                                                />
                                            
                                            <!-- and all other head elements -->
                                            
                                                
                                                    </
                                                    head
                                                
                                                >
                                            
                                            
                                                
                                                    <
                                                    body
                                                
                                                >
                                            
                                            <!-- your page content -->
                                            
                                                
                                                    </
                                                    body
                                                
                                                >
                                            
                                            
                                                
                                                    </
                                                    html
                                                
                                                >
                                            
                                        
                                    

You should place dns-prefetch hints in the <head > element any time your site references resources on cross-origin domains, but there are some things to keep in mind.

Best practices

There are 3 main things to keep in mind:

For one , dns-prefetch is only effective for DNS lookups on cross-origin domains, so avoid using it to point to your site or domain. This is because the IP behind your site's domain will have already been resolved by the time the browser sees the hint.

Second , it's also possible to specify dns-prefetch (and other resources hints) as an HTTP header by using the HTTP Link field :

http
                                        
                                            
                                                Link
                                                :
                                                <https://fonts.googleapis.com/>; rel=dns-prefetch
                                            
                                        
                                    

Third , while dns-prefetch only performs a DNS lookup, preconnect establishes a connection to a server. This process includes DNS resolution, as well as establishing the TCP connection, and performing the TLS handshake—if a site is served over HTTPS. Using preconnect provides an opportunity to further reduce the perceived latency of cross-origin requests . You can use it as an HTTP header by using the HTTP Link field :

http
                                        
                                            
                                                Link
                                                :
                                                <https://fonts.googleapis.com/>; rel=preconnect
                                            
                                        
                                    

or via the HTML <link > element :

html
                                        
                                            
                                                
                                                    <
                                                    link
                                                
                                                rel
                                                
                                                    =
                                                    "
                                                    preconnect"
                                                
                                                href
                                                
                                                    =
                                                    "
                                                    https://fonts.googleapis.com/"
                                                
                                                crossorigin
                                                />
                                            
                                        
                                    

Note: If a page needs to make connections to many third-party domains, preconnecting them all is counterproductive. The preconnect hint is best used for only the most critical connections. For the others, just use <link rel="dns-prefetch"> to save time on the first step — the DNS lookup.

The logic behind pairing these hints is because support for dns-prefetch is better than support for preconnect. Browsers that don't support preconnect will still get some added benefit by falling back to dns-prefetch. Because this is an HTML feature, it is very fault-tolerant. If a non-supporting browser encounters a dns-prefetch hint—or any other resource hint—your site won't break. You just won't receive the benefits it provides.

Some resources such as fonts are loaded in anonymous mode. In such cases you should set the crossorigin attribute with the preconnect hint. If you omit it, the browser will only perform the DNS lookup.

See also

Updated on April 20, 2024 by Datarist.