<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <id>https://web.dev/</id>
  <title>Yusuke Utsunomiya on web.dev</title>
  <updated>2026-04-15T23:21:06Z</updated>
  <author>
    <name>Yusuke Utsunomiya</name>
  </author>
  <link href="https://web.dev/authors/uskay/feed.xml" rel="self"/>
  <link href="https://web.dev/"/>
  <icon>https://web-dev.imgix.net/image/admin/ztcrpMPYTd84oPTN4GdY.jpg?auto=format</icon>
  <logo>https://web.dev/images/shared/rss-banner.png</logo>
  <subtitle>Tech consultant for Web, Chrome &amp;amp; AMP</subtitle>
  
  
  <entry>
    <title>Five ways AirSHIFT improved their React app&#39;s runtime performance</title>
    <link href="https://web.dev/five-ways-airshift-improved-their-react-app/"/>
    <updated>2019-11-06T00:00:00Z</updated>
    <id>https://web.dev/five-ways-airshift-improved-their-react-app/</id>
    <content type="html" mode="escaped">&lt;p&gt;Website performance is not just about load time. It is critical to provide a fast and responsive experience to users, especially for productivity desktop apps which people use everyday. The engineering team at &lt;a href=&quot;https://recruit-tech.co.jp/&quot; rel=&quot;noopener&quot;&gt;Recruit Technologies&lt;/a&gt; went through a refactoring project to improve one of their web apps, &lt;a href=&quot;https://airregi.jp/shift/&quot; rel=&quot;noopener&quot;&gt;AirSHIFT&lt;/a&gt;, for better user input performance. Here&#39;s how they did it.&lt;/p&gt;
&lt;h2 id=&quot;slow-response,-less-productivity&quot;&gt;Slow response, less productivity &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/five-ways-airshift-improved-their-react-app/#slow-response,-less-productivity&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;AirSHIFT is a desktop web application that helps store owners, like restaurants and cafes, to manage the shift work of their staff members. Built with React, the single page application provides rich client features including various grid tables of shift schedules organized by day, week, month and more.&lt;/p&gt;
&lt;img alt=&quot;A screenshot of the AirSHIFT web app.&quot; decoding=&quot;async&quot; height=&quot;626&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/q7g5sY2ix1oTwoPJX7qX.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/q7g5sY2ix1oTwoPJX7qX.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/q7g5sY2ix1oTwoPJX7qX.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/q7g5sY2ix1oTwoPJX7qX.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/q7g5sY2ix1oTwoPJX7qX.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/q7g5sY2ix1oTwoPJX7qX.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/q7g5sY2ix1oTwoPJX7qX.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/q7g5sY2ix1oTwoPJX7qX.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/q7g5sY2ix1oTwoPJX7qX.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/q7g5sY2ix1oTwoPJX7qX.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/q7g5sY2ix1oTwoPJX7qX.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/q7g5sY2ix1oTwoPJX7qX.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/q7g5sY2ix1oTwoPJX7qX.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/q7g5sY2ix1oTwoPJX7qX.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/q7g5sY2ix1oTwoPJX7qX.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/q7g5sY2ix1oTwoPJX7qX.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/q7g5sY2ix1oTwoPJX7qX.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/q7g5sY2ix1oTwoPJX7qX.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;As the Recruit Technologies engineering team added new features to the
AirSHIFT app, they started seeing more feedback around slow performance.
The engineering manager of AirSHIFT, Yosuke Furukawa, said:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;In a user research study, we were shocked when one of the store owners said she
  would leave her seat to brew coffee after clicking a button, just to kill time waiting
  for the shift table to load.&lt;/p&gt;
  &lt;cite&gt;&lt;/cite&gt;
&lt;/blockquote&gt;
&lt;p&gt;After going through the research, the engineering team realized that many of their users were trying to load massive shift tables on low spec computers, such as a 1 GHz Celeron M laptop from 10 years ago.&lt;/p&gt;
&lt;figure&gt;
  &lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot;&gt;
    &lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/airshift-perf-optimization/endless_spinner_vp9.webm&quot; type=&quot;video/webm; codecs=vp8&quot; /&gt;
    &lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/airshift-perf-optimization/endless_spinner_h264.mp4&quot; type=&quot;video/mp4; codecs=h264&quot; /&gt;
  &lt;/video&gt;
 &lt;figcaption&gt;
    Endless spinner on low end devices.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The AirSHIFT app was blocking the main thread with expensive scripts,
but the engineering team didn&#39;t realize how expensive the scripts were because they
were developing and testing on rich spec computers with fast Wi-Fi connections.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;A chart that shows the app&amp;#x27;s runtime activity.&quot; decoding=&quot;async&quot; height=&quot;476&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/RAi3zG4JngSi0UlSMi0a.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/RAi3zG4JngSi0UlSMi0a.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/RAi3zG4JngSi0UlSMi0a.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/RAi3zG4JngSi0UlSMi0a.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/RAi3zG4JngSi0UlSMi0a.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/RAi3zG4JngSi0UlSMi0a.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/RAi3zG4JngSi0UlSMi0a.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/RAi3zG4JngSi0UlSMi0a.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/RAi3zG4JngSi0UlSMi0a.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/RAi3zG4JngSi0UlSMi0a.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/RAi3zG4JngSi0UlSMi0a.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/RAi3zG4JngSi0UlSMi0a.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/RAi3zG4JngSi0UlSMi0a.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/RAi3zG4JngSi0UlSMi0a.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/RAi3zG4JngSi0UlSMi0a.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/RAi3zG4JngSi0UlSMi0a.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/RAi3zG4JngSi0UlSMi0a.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/RAi3zG4JngSi0UlSMi0a.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    When loading the shift table, around 80% of the load time was consumed by running scripts.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;After profiling their performance in Chrome DevTools with CPU and network throttling enabled,
it became clear that performance optimization was needed.
AirSHIFT formed a task force to tackle this issue. Here are 5 things
they focused on to make their app more responsive to user input.&lt;/p&gt;
&lt;h2 id=&quot;1-virtualize-large-tables&quot;&gt;1. Virtualize large tables &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/five-ways-airshift-improved-their-react-app/#1-virtualize-large-tables&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Displaying the shift table required multiple expensive steps: constructing the virtual DOM and rendering it on screen in proportion to the number of staff members and time slots. For example, if a restaurant had 50 working members and wanted to check their monthly shift schedule, it would be a table of 50 (members) multiplied by 30 (days) which would lead to 1,500 cell components to render. This is a very expensive operation, especially for low spec devices. In reality, things were worse. From the research they learned there were shops managing 200 staff members, requiring around 6,000 cell components in a single monthly table.&lt;/p&gt;
&lt;p&gt;To reduce the cost of this operation, AirSHIFT virtualized the shift table. The app now only mounts the components within the viewport and unmounts the off-screen components.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;An annotated screenshot that demonstrates that AirSHIFT used to render content outside of the viewport.&quot; decoding=&quot;async&quot; height=&quot;306&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QOVmOQa0Jryhg8tKPBFs.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QOVmOQa0Jryhg8tKPBFs.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QOVmOQa0Jryhg8tKPBFs.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QOVmOQa0Jryhg8tKPBFs.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QOVmOQa0Jryhg8tKPBFs.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QOVmOQa0Jryhg8tKPBFs.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QOVmOQa0Jryhg8tKPBFs.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QOVmOQa0Jryhg8tKPBFs.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QOVmOQa0Jryhg8tKPBFs.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QOVmOQa0Jryhg8tKPBFs.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QOVmOQa0Jryhg8tKPBFs.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QOVmOQa0Jryhg8tKPBFs.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QOVmOQa0Jryhg8tKPBFs.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QOVmOQa0Jryhg8tKPBFs.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QOVmOQa0Jryhg8tKPBFs.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QOVmOQa0Jryhg8tKPBFs.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QOVmOQa0Jryhg8tKPBFs.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QOVmOQa0Jryhg8tKPBFs.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    Before: Rendering all the shift table cells.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;img alt=&quot;An annotated screenshot that demonstrates that AirSHIFT now only renders content that&amp;#x27;s visible in the viewport.&quot; decoding=&quot;async&quot; height=&quot;307&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NLSdFKj1gpGBO82pfNNO.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NLSdFKj1gpGBO82pfNNO.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NLSdFKj1gpGBO82pfNNO.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NLSdFKj1gpGBO82pfNNO.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NLSdFKj1gpGBO82pfNNO.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NLSdFKj1gpGBO82pfNNO.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NLSdFKj1gpGBO82pfNNO.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NLSdFKj1gpGBO82pfNNO.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NLSdFKj1gpGBO82pfNNO.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NLSdFKj1gpGBO82pfNNO.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NLSdFKj1gpGBO82pfNNO.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NLSdFKj1gpGBO82pfNNO.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NLSdFKj1gpGBO82pfNNO.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NLSdFKj1gpGBO82pfNNO.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NLSdFKj1gpGBO82pfNNO.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NLSdFKj1gpGBO82pfNNO.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NLSdFKj1gpGBO82pfNNO.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NLSdFKj1gpGBO82pfNNO.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    After: Only rendering the cells within the viewport.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;In this case, AirSHIFT used &lt;a href=&quot;https://github.com/bvaughn/react-virtualized&quot; rel=&quot;noopener&quot;&gt;react-virtualized&lt;/a&gt; as there were requirements around enabling complex two dimensional grid tables. They are also exploring ways to convert the implementation to use the lightweight &lt;a href=&quot;https://web.dev/virtualize-long-lists-react-window/&quot;&gt;react-window&lt;/a&gt; in the future.&lt;/p&gt;
&lt;h3 id=&quot;results&quot;&gt;Results &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/five-ways-airshift-improved-their-react-app/#results&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Virtualizing the table alone reduced scripting time by 6 seconds (on a 4x CPU slowdown + Fast 3G throttled Macbook Pro environment). This was the most impactful performance improvement in the refactoring project.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;An annotated screenshot of a Chrome DevTools Performance panel recording.&quot; decoding=&quot;async&quot; height=&quot;289&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pRX1ap7tVOdAoTo35MWj.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pRX1ap7tVOdAoTo35MWj.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pRX1ap7tVOdAoTo35MWj.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pRX1ap7tVOdAoTo35MWj.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pRX1ap7tVOdAoTo35MWj.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pRX1ap7tVOdAoTo35MWj.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pRX1ap7tVOdAoTo35MWj.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pRX1ap7tVOdAoTo35MWj.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pRX1ap7tVOdAoTo35MWj.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pRX1ap7tVOdAoTo35MWj.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pRX1ap7tVOdAoTo35MWj.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pRX1ap7tVOdAoTo35MWj.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pRX1ap7tVOdAoTo35MWj.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pRX1ap7tVOdAoTo35MWj.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pRX1ap7tVOdAoTo35MWj.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pRX1ap7tVOdAoTo35MWj.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pRX1ap7tVOdAoTo35MWj.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pRX1ap7tVOdAoTo35MWj.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    Before: Around 10 seconds of scripting after user input.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Another annotated screenshot of a Chrome DevTools Performance panel recording.&quot; decoding=&quot;async&quot; height=&quot;358&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bCy17DjlhMWXSsN1cFAZ.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bCy17DjlhMWXSsN1cFAZ.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bCy17DjlhMWXSsN1cFAZ.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bCy17DjlhMWXSsN1cFAZ.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bCy17DjlhMWXSsN1cFAZ.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bCy17DjlhMWXSsN1cFAZ.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bCy17DjlhMWXSsN1cFAZ.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bCy17DjlhMWXSsN1cFAZ.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bCy17DjlhMWXSsN1cFAZ.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bCy17DjlhMWXSsN1cFAZ.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bCy17DjlhMWXSsN1cFAZ.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bCy17DjlhMWXSsN1cFAZ.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bCy17DjlhMWXSsN1cFAZ.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bCy17DjlhMWXSsN1cFAZ.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bCy17DjlhMWXSsN1cFAZ.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bCy17DjlhMWXSsN1cFAZ.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bCy17DjlhMWXSsN1cFAZ.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bCy17DjlhMWXSsN1cFAZ.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    After: 4 seconds of scripting after user input.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;2-audit-with-user-timing-api&quot;&gt;2. Audit with User Timing API &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/five-ways-airshift-improved-their-react-app/#2-audit-with-user-timing-api&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Next, the AirSHIFT team refactored the scripts that run on user input.
The &lt;a href=&quot;https://developer.chrome.com/docs/devtools/evaluate-performance/reference/#main&quot; rel=&quot;noopener&quot;&gt;flame chart&lt;/a&gt;
of &lt;a href=&quot;https://developer.chrome.com/docs/devtools/&quot; rel=&quot;noopener&quot;&gt;Chrome DevTools&lt;/a&gt;
makes it possible to analyze what&#39;s actually happening in the main thread.
But the AirSHIFT team found it easier to analyze application activity based
on React&#39;s lifecycle.&lt;/p&gt;
&lt;p&gt;React 16 provides its performance trace via the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/User_Timing_API&quot; rel=&quot;noopener&quot;&gt;User Timing API&lt;/a&gt;,
which you can visualize from the
&lt;a href=&quot;https://developer.chrome.com/blog/new-in-devtools-67/#tabs&quot; rel=&quot;noopener&quot;&gt;Timings section&lt;/a&gt;
of Chrome DevTools. AirSHIFT used the Timings section to find
unnecessary logic running in React lifecycle events.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The Timings section of the Performance panel of Chrome DevTools.&quot; decoding=&quot;async&quot; height=&quot;388&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/zTvUhCKRSCvZzo9mlkte.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/zTvUhCKRSCvZzo9mlkte.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/zTvUhCKRSCvZzo9mlkte.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/zTvUhCKRSCvZzo9mlkte.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/zTvUhCKRSCvZzo9mlkte.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/zTvUhCKRSCvZzo9mlkte.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/zTvUhCKRSCvZzo9mlkte.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/zTvUhCKRSCvZzo9mlkte.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/zTvUhCKRSCvZzo9mlkte.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/zTvUhCKRSCvZzo9mlkte.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/zTvUhCKRSCvZzo9mlkte.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/zTvUhCKRSCvZzo9mlkte.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/zTvUhCKRSCvZzo9mlkte.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/zTvUhCKRSCvZzo9mlkte.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/zTvUhCKRSCvZzo9mlkte.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/zTvUhCKRSCvZzo9mlkte.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/zTvUhCKRSCvZzo9mlkte.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/zTvUhCKRSCvZzo9mlkte.png?auto=format&amp;w=1600 1600w&quot; style=&quot;max-width: 75%;&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    React&#39;s User Timing events.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; Related article: &lt;a href=&quot;https://reactjs.org/docs/optimizing-performance.html#profiling-components-with-the-chrome-performance-tab&quot;&gt;Profiling Components with the Chrome Performance Tab&lt;/a&gt; &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;results-2&quot;&gt;Results &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/five-ways-airshift-improved-their-react-app/#results-2&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The AirSHIFT team discovered that an unnecessary
&lt;a href=&quot;https://reactjs.org/docs/reconciliation.html&quot; rel=&quot;noopener&quot;&gt;React Tree Reconciliation&lt;/a&gt;
was happening right before every route navigation. This meant that
React was updating the shift table unnecessarily before navigations.
An unnecessary Redux state update was causing this issue.
Fixing it saved around 750 ms of scripting time. AirSHIFT
made other micro optimizations as well which eventually led to
a 1 second total reduction in scripting time.&lt;/p&gt;
&lt;h2 id=&quot;3-lazy-load-components-and-move-expensive-logic-to-web-workers&quot;&gt;3. Lazy load components and move expensive logic to web workers &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/five-ways-airshift-improved-their-react-app/#3-lazy-load-components-and-move-expensive-logic-to-web-workers&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;AirSHIFT has a built-in chat application. Many store owners communicate with their staff members via the chat while looking at the shift table, which means that a user might be typing a message while the table is loading. If the main thread is occupied with scripts that are rendering the table, user input could be janky.&lt;/p&gt;
&lt;p&gt;To improve this experience, AirSHIFT now uses &lt;a href=&quot;https://web.dev/code-splitting-suspense/&quot;&gt;React.lazy and Suspense&lt;/a&gt; to show placeholders for table contents while lazily loading the actual components.&lt;/p&gt;
&lt;p&gt;The AirSHIFT team also migrated some of the expensive business logic
within the lazily loaded components to
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Web_Workers_API/Using_web_workers&quot; rel=&quot;noopener&quot;&gt;web workers&lt;/a&gt;.
This solved the user input jank problem by freeing up the main thread
so that it could focus on responding to user input.&lt;/p&gt;
&lt;p&gt;Typically developers face complexity in using workers but this time &lt;a href=&quot;https://github.com/GoogleChromeLabs/comlink&quot; rel=&quot;noopener&quot;&gt;Comlink&lt;/a&gt; did the heavy lifting for them. Below is the pseudo code of how AirSHIFT workerized one of the most expensive operations they had: calculating total labor costs.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;In App.js, use React.lazy and Suspense to show fallback content while loading&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/** App.js */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; lazy&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Suspense &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;react&#39;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Lazily loading the Cost component with React.lazy&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; Hello &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;lazy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./Cost&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;Loading&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;Some fallback content to show &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; loading&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Showing the fallback content while loading the Cost component by Suspense&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;App&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; userInfo &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;   &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Suspense fallback&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Loading &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Cost &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;Suspense&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;&lt;em&gt;In the Cost component, use comlink to execute the calc logic&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/** Cost.js */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; React &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;react&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; proxy &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;comlink&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// import the workerlized calc function with comlink&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; WorkerlizedCostCalc &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;proxy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Worker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./WorkerlizedCostCalc.js&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Cost&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; userInfo &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// execute the calculation in the worker&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; instance &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WorkerlizedCostCalc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; cost &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; instance&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;userInfo&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;cost&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;p&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;&lt;em&gt;Implement the calculation logic that runs in the worker and expose it with comlink&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// WorkerlizedCostCalc.js&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; expose &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;comlink&#39;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; someExpensiveCalculation &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;./CostCalc.js&#39;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Expose the new workerlized calc function with comlink&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;expose&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;userInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// run existing (expensive) function in the worker&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;someExpensiveCalculation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;userInfo&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; Related article: &lt;a href=&quot;https://dassur.ma/things/react-redux-comlink/&quot;&gt;React + Redux + Comlink = Off-main-thread&lt;/a&gt; &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;results-3&quot;&gt;Results &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/five-ways-airshift-improved-their-react-app/#results-3&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Despite the limited amount of logic they workerized as a trial, AirSHIFT shifted around 100 ms of
their JavaScript from the main thread to the worker thread (simulated with 4x CPU throttling).&lt;/p&gt;
&lt;img alt=&quot;A screenshot of a Chrome DevTools Performance panel recording that shows that scripting is now occurring on a web worker rather than the main thread.&quot; decoding=&quot;async&quot; height=&quot;99&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cFIFNeEKqbyWGBEuPCy2.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cFIFNeEKqbyWGBEuPCy2.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cFIFNeEKqbyWGBEuPCy2.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cFIFNeEKqbyWGBEuPCy2.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cFIFNeEKqbyWGBEuPCy2.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cFIFNeEKqbyWGBEuPCy2.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cFIFNeEKqbyWGBEuPCy2.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cFIFNeEKqbyWGBEuPCy2.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cFIFNeEKqbyWGBEuPCy2.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cFIFNeEKqbyWGBEuPCy2.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cFIFNeEKqbyWGBEuPCy2.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cFIFNeEKqbyWGBEuPCy2.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cFIFNeEKqbyWGBEuPCy2.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cFIFNeEKqbyWGBEuPCy2.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cFIFNeEKqbyWGBEuPCy2.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cFIFNeEKqbyWGBEuPCy2.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cFIFNeEKqbyWGBEuPCy2.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cFIFNeEKqbyWGBEuPCy2.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;AirSHIFT is currently exploring whether they can lazy load other components
and offload more logic to web workers to further reduce jank.&lt;/p&gt;
&lt;h2 id=&quot;4-setting-a-performance-budget&quot;&gt;4. Setting a performance budget &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/five-ways-airshift-improved-their-react-app/#4-setting-a-performance-budget&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Having implemented all of these optimizations, it was critical to make sure that the app remains
performant over time. AirSHIFT now uses &lt;a href=&quot;https://github.com/siddharthkp/bundlesize&quot; rel=&quot;noopener&quot;&gt;bundlesize&lt;/a&gt; to
not exceed the current JavaScript and CSS file size. Aside from setting these basic budgets, they
built a dashboard to show various percentiles of the shift table loading time to check whether the
application is performant even in non-ideal conditions.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The script completion time for every Redux event is now measured&lt;/li&gt;
&lt;li&gt;Performance data is collected in &lt;a href=&quot;https://www.elastic.co/jp/&quot; rel=&quot;noopener&quot;&gt;Elasticsearch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;10th, 25th, 50th, and 75th percentile performance of each event is visualized with &lt;a href=&quot;https://www.elastic.co/jp/products/kibana&quot; rel=&quot;noopener&quot;&gt;Kibana&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;AirSHIFT is now monitoring the shift table loading event to make sure it completes in 3 seconds for
the 75th percentile users. This is an unenforced budget for now but they are considering auto-notifications
via Elasticsearch when they exceed their budget.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;A chart showing that the 75th percentile completes in around 2500 ms, the 50th percentile in around 1250 ms, the 25th percentile in around 750 ms, and the 10th percentile in around 500 ms.&quot; decoding=&quot;async&quot; height=&quot;549&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pSRqSKpmHKYGg8jalEr7.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pSRqSKpmHKYGg8jalEr7.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pSRqSKpmHKYGg8jalEr7.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pSRqSKpmHKYGg8jalEr7.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pSRqSKpmHKYGg8jalEr7.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pSRqSKpmHKYGg8jalEr7.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pSRqSKpmHKYGg8jalEr7.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pSRqSKpmHKYGg8jalEr7.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pSRqSKpmHKYGg8jalEr7.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pSRqSKpmHKYGg8jalEr7.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pSRqSKpmHKYGg8jalEr7.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pSRqSKpmHKYGg8jalEr7.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pSRqSKpmHKYGg8jalEr7.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pSRqSKpmHKYGg8jalEr7.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pSRqSKpmHKYGg8jalEr7.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pSRqSKpmHKYGg8jalEr7.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pSRqSKpmHKYGg8jalEr7.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pSRqSKpmHKYGg8jalEr7.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    The Kibana dashboard showing daily performance data by percentiles.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; Related article: &lt;a href=&quot;https://web.dev/performance-budgets-101&quot;&gt;Performance budgets 101&lt;/a&gt; &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;results-4&quot;&gt;Results &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/five-ways-airshift-improved-their-react-app/#results-4&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;From the graph above, you can tell that AirSHIFT is now mostly hitting the 3 seconds budget for 75th percentile users and also loading the shift table within a second for 25th percentile users. By capturing RUM performance data from various conditions and devices, AirSHIFT can now check whether a new feature release is actually affecting the application&#39;s performance or not.&lt;/p&gt;
&lt;h2 id=&quot;5-performance-hackathons&quot;&gt;5. Performance hackathons &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/five-ways-airshift-improved-their-react-app/#5-performance-hackathons&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Even though all of these performance optimization efforts were important and impactful,
it&#39;s not always easy to get engineering and business teams to prioritize non-functional
development. Part of the challenge is that some of these performance optimizations
can&#39;t be planned. They require experimentation and a trial-and-error mindset.&lt;/p&gt;
&lt;p&gt;AirSHIFT is now conducting internal 1-day performance hackathons to let engineers focus only on performance related work. In these hackathons they remove all constraints and respect the engineers&#39; creativity, meaning any implementation that contributes to speed is worth considering. To accelerate the hackathon, AirSHIFT splits the group into small teams and each team competes to see who can get the biggest &lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/overview/&quot; rel=&quot;noopener&quot;&gt;Lighthouse&lt;/a&gt; performance score improvement.
The teams get very competitive! 🔥&lt;/p&gt;
&lt;img alt=&quot;Photos of the hackathon.&quot; decoding=&quot;async&quot; height=&quot;462&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/A1nus9lmROXOGm9rpCsO.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/A1nus9lmROXOGm9rpCsO.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/A1nus9lmROXOGm9rpCsO.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/A1nus9lmROXOGm9rpCsO.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/A1nus9lmROXOGm9rpCsO.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/A1nus9lmROXOGm9rpCsO.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/A1nus9lmROXOGm9rpCsO.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/A1nus9lmROXOGm9rpCsO.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/A1nus9lmROXOGm9rpCsO.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/A1nus9lmROXOGm9rpCsO.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/A1nus9lmROXOGm9rpCsO.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/A1nus9lmROXOGm9rpCsO.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/A1nus9lmROXOGm9rpCsO.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/A1nus9lmROXOGm9rpCsO.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/A1nus9lmROXOGm9rpCsO.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/A1nus9lmROXOGm9rpCsO.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/A1nus9lmROXOGm9rpCsO.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/A1nus9lmROXOGm9rpCsO.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;h3 id=&quot;results-5&quot;&gt;Results &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/five-ways-airshift-improved-their-react-app/#results-5&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The hackathon approach is working well for them.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Performance bottlenecks can be easily detected by actually trying out multiple approaches during the hackathon and measuring each with Lighthouse.&lt;/li&gt;
&lt;li&gt;After the hackathon, it&#39;s rather easy to convince the team which optimization they should be prioritizing for production release.&lt;/li&gt;
&lt;li&gt;It&#39;s also an effective way of advocating the importance of speed. Every participant can understand the correlation between how you code and how it results in performance.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A good side effect was that many other engineering teams within Recruit got interested in this hands-on approach and the AirSHIFT team is now facilitating multiple speed hackathons within the company.&lt;/p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/five-ways-airshift-improved-their-react-app/#summary&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It was definitely not the easiest journey for AirSHIFT to work on these optimizations but it certainly paid off. Now AirSHIFT is loading the shift table within 1.5 sec in median which is a 6x improvement from their performance
before the project.&lt;/p&gt;
&lt;figure&gt;
  &lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot;&gt;
    &lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/airshift-perf-optimization/compare_speed_vp9.webm&quot; type=&quot;video/webm; codecs=vp8&quot; /&gt;
    &lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/airshift-perf-optimization/compare_speed_h264.mp4&quot; type=&quot;video/mp4; codecs=h264&quot; /&gt;
  &lt;/video&gt;
&lt;/figure&gt;
&lt;p&gt;After the performance optimizations launched, one user said:&lt;/p&gt;
&lt;blockquote class=&quot;blockquote&quot;&gt;
  Thank you so much for making the shift table load fast.
  Arranging the shift work is so much more efficient now.
  &lt;cite&gt;&lt;/cite&gt;
&lt;/blockquote&gt;
</content>
    <author>
      <name>Yusuke Utsunomiya</name>
    </author><author>
      <name>Yosuke Furukawa</name>
    </author><author>
      <name>Satoshi Arai</name>
    </author><author>
      <name>Kento Tsuji</name>
    </author>
  </entry>
  
  <entry>
    <title>Hands-on with Portals: seamless navigation on the web</title>
    <link href="https://web.dev/hands-on-portals/"/>
    <updated>2019-05-06T00:00:00Z</updated>
    <id>https://web.dev/hands-on-portals/</id>
    <content type="html" mode="escaped">&lt;p&gt;Making sure your pages load fast is key to delivering a good user experience.
But one area we often overlook is page transitions—what our users see when
they move between pages.&lt;/p&gt;
&lt;p&gt;A new web platform API proposal called &lt;a href=&quot;https://github.com/WICG/portals&quot; rel=&quot;noopener&quot;&gt;Portals&lt;/a&gt; aims to
help with this by streamlining the experience as users navigate &lt;em&gt;across&lt;/em&gt; your
site.&lt;/p&gt;
&lt;p&gt;See Portals in action:&lt;/p&gt;
&lt;figure data-size=&quot;full&quot;&gt;
  &lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot;&gt;
    &lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/hands-on-portals/portals_vp9.webm&quot; type=&quot;video/webm; codecs=vp8&quot; /&gt;
    &lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/hands-on-portals/portals_h264.mp4&quot; type=&quot;video/mp4; codecs=h264&quot; /&gt;
  &lt;/video&gt;
 &lt;figcaption&gt;
    Seamless embeds and navigation with Portals. Created by &lt;a href=&quot;https://twitter.com/argyleink&quot;&gt;Adam Argyle&lt;/a&gt;.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;what-portals-enable&quot;&gt;What Portals enable &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/hands-on-portals/#what-portals-enable&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Single Page Applications (SPAs) offer nice transitions
but come at the cost of higher complexity to build.
Multi-page Applications (MPAs) are much easier to build,
but you end up with blank screens between pages.&lt;/p&gt;
&lt;p&gt;Portals offer the best of both worlds:
the low complexity of an MPA with the seamless transitions of an SPA.
Think of them like an &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; in that they allow for embedding,
but unlike an &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt;,
they also come with features to navigate to their content.&lt;/p&gt;
&lt;p&gt;Seeing is believing:
please first check out what we showcased at Chrome Dev Summit 2018:&lt;/p&gt;
&lt;div class=&quot;youtube&quot;&gt;  &lt;lite-youtube videoid=&quot;Ai4aZ9Jbsys&quot; videoStartAt=&quot;1081&quot;&gt;  &lt;/lite-youtube&gt;&lt;/div&gt;
&lt;p&gt;With classic navigations, users have to wait with a blank screen
until the browser finishes rendering the destination.
With Portals, users get to experience an animation,
while the &lt;code&gt;&amp;lt;portal&amp;gt;&lt;/code&gt; pre-renders content and creates a seamless navigation experience.&lt;/p&gt;
&lt;p&gt;Before Portals, we could have rendered another page using an &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt;. We could also have added animations to move the frame around the page. But an &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; won&#39;t let you navigate into its content. Portals close this gap, enabling interesting use cases.&lt;/p&gt;
&lt;h2 id=&quot;try-out-portals&quot;&gt;Try out Portals &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/hands-on-portals/#try-out-portals&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;enable-flags&quot;&gt;Enabling via about://flags &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/hands-on-portals/#enable-flags&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Try out Portals in Chrome 85 and later versions by flipping an experimental flag:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Enable the &lt;code&gt;about://flags/#enable-portals&lt;/code&gt; flag for same-origin navigations.&lt;/li&gt;
&lt;li&gt;For testing out cross-origin navigations, enable the &lt;code&gt;about://flags/#enable-portals-cross-origin&lt;/code&gt; flag in addition.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;During this early phase of the Portals experiment,
we also recommend using a completely separate user data directory for your tests
by setting the
&lt;a href=&quot;https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md#command-line&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;--user-data-dir&lt;/code&gt;&lt;/a&gt;
command line flag.
Once Portals are enabled, confirm in DevTools that you have the new shiny &lt;code&gt;HTMLPortalElement&lt;/code&gt;.&lt;/p&gt;
&lt;img alt=&quot;A screenshot of the DevTools console showing the HTMLPortalElement&quot; decoding=&quot;async&quot; height=&quot;252&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/aUrrqhzMxaEX865Fk5zX.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/aUrrqhzMxaEX865Fk5zX.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/aUrrqhzMxaEX865Fk5zX.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/aUrrqhzMxaEX865Fk5zX.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/aUrrqhzMxaEX865Fk5zX.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/aUrrqhzMxaEX865Fk5zX.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/aUrrqhzMxaEX865Fk5zX.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/aUrrqhzMxaEX865Fk5zX.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/aUrrqhzMxaEX865Fk5zX.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/aUrrqhzMxaEX865Fk5zX.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/aUrrqhzMxaEX865Fk5zX.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/aUrrqhzMxaEX865Fk5zX.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/aUrrqhzMxaEX865Fk5zX.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/aUrrqhzMxaEX865Fk5zX.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/aUrrqhzMxaEX865Fk5zX.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/aUrrqhzMxaEX865Fk5zX.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/aUrrqhzMxaEX865Fk5zX.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/aUrrqhzMxaEX865Fk5zX.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;h2 id=&quot;implement-portals&quot;&gt;Implement Portals &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/hands-on-portals/#implement-portals&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Let&#39;s walk through a basic implementation example.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Create a portal with the wikipedia page, and embed it&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// (like an iframe). You can also use the &amp;lt;portal&gt; tag instead.&lt;/span&gt;&lt;br /&gt;portal &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;portal&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;portal&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;src &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;https://en.wikipedia.org/wiki/World_Wide_Web&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;portal&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;...&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendChild&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;portal&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// When the user touches the preview (embedded portal):&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// do fancy animation, e.g. expand …&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// and finish by doing the actual transition.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// For the sake of simplicity, this snippet will navigate&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// on the `onload` event of the Portals element.&lt;/span&gt;&lt;br /&gt;portal&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;load&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;evt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;   portal&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;activate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;It&#39;s that simple. Try this code in the DevTools console, the wikipedia page should open up.&lt;/p&gt;
&lt;img alt=&quot;A gif of preview portal style demo&quot; decoding=&quot;async&quot; height=&quot;557&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/rp6i8ngGJkvooXJ9WmLK.gif?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/rp6i8ngGJkvooXJ9WmLK.gif?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/rp6i8ngGJkvooXJ9WmLK.gif?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/rp6i8ngGJkvooXJ9WmLK.gif?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/rp6i8ngGJkvooXJ9WmLK.gif?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/rp6i8ngGJkvooXJ9WmLK.gif?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/rp6i8ngGJkvooXJ9WmLK.gif?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/rp6i8ngGJkvooXJ9WmLK.gif?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/rp6i8ngGJkvooXJ9WmLK.gif?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/rp6i8ngGJkvooXJ9WmLK.gif?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/rp6i8ngGJkvooXJ9WmLK.gif?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/rp6i8ngGJkvooXJ9WmLK.gif?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/rp6i8ngGJkvooXJ9WmLK.gif?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/rp6i8ngGJkvooXJ9WmLK.gif?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/rp6i8ngGJkvooXJ9WmLK.gif?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/rp6i8ngGJkvooXJ9WmLK.gif?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/rp6i8ngGJkvooXJ9WmLK.gif?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/rp6i8ngGJkvooXJ9WmLK.gif?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;If you wanted to build something like we showed at Chrome Dev Summit which works just like the demo above,
the following snippet will be of interest.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Adding some styles with transitions&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; style &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;style&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;innerHTML &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&lt;br /&gt;  portal {&lt;br /&gt;    position:fixed;&lt;br /&gt;    width: 100%;&lt;br /&gt;    height: 100%;&lt;br /&gt;    opacity: 0;&lt;br /&gt;    box-shadow: 0 0 20px 10px #999;&lt;br /&gt;    transform: scale(0.4);&lt;br /&gt;    transform-origin: bottom left;&lt;br /&gt;    bottom: 20px;&lt;br /&gt;    left: 20px;&lt;br /&gt;    animation-name: fade-in;&lt;br /&gt;    animation-duration: 1s;&lt;br /&gt;    animation-delay: 2s;&lt;br /&gt;    animation-fill-mode: forwards;&lt;br /&gt;  }&lt;br /&gt;  .portal-transition {&lt;br /&gt;    transition: transform 0.4s;&lt;br /&gt;  }&lt;br /&gt;  @media (prefers-reduced-motion: reduce) {&lt;br /&gt;    .portal-transition {&lt;br /&gt;      transition: transform 0.001s;&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;  .portal-reveal {&lt;br /&gt;    transform: scale(1.0) translateX(-20px) translateY(20px);&lt;br /&gt;  }&lt;br /&gt;  @keyframes fade-in {&lt;br /&gt;    0%   { opacity: 0; }&lt;br /&gt;    100% { opacity: 1; }&lt;br /&gt;  }&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; portal &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;portal&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Let&#39;s navigate into the WICG Portals spec page&lt;/span&gt;&lt;br /&gt;portal&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;src &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;https://wicg.github.io/portals/&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Add a class that defines the transition. Consider using&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// `prefers-reduced-motion` media query to control the animation.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// https://developers.google.com/web/updates/2019/03/prefers-reduced-motion&lt;/span&gt;&lt;br /&gt;portal&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;classList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;portal-transition&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;portal&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;click&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;evt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Animate the portal once user interacts&lt;/span&gt;&lt;br /&gt;  portal&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;classList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;portal-reveal&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;portal&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;transitionend&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;evt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;evt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;propertyName &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;transform&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Activate the portal once the transition has completed&lt;/span&gt;&lt;br /&gt;    portal&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;activate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; portal&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;It is also easy to do feature detection to progressively enhance a website using Portals.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;HTMLPortalElement&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// If this is a platform that have Portals...&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; portal &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;portal&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;If you want to quickly experience what Portals feel like, try using
&lt;a href=&quot;https://uskay-portals-demo.glitch.me/&quot; rel=&quot;noopener&quot;&gt;uskay-portals-demo.glitch.me&lt;/a&gt;.
Be sure you access it with Chrome 85 or later versions and turn on the &lt;a href=&quot;https://web.dev/hands-on-portals/#enable-flags&quot;&gt;experimental flag&lt;/a&gt;!&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Enter a URL you want to preview.&lt;/li&gt;
&lt;li&gt;The page will then be embedded as a &lt;code&gt;&amp;lt;portal&amp;gt;&lt;/code&gt; element.&lt;/li&gt;
&lt;li&gt;Click on the preview.&lt;/li&gt;
&lt;li&gt;The preview will be activated after an animation.&lt;/li&gt;
&lt;/ol&gt;
&lt;img alt=&quot;A gif of using the glitch demo of using Portals&quot; decoding=&quot;async&quot; height=&quot;547&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/Y4Vv6v3DAAC32IsiWS7g.gif?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/Y4Vv6v3DAAC32IsiWS7g.gif?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/Y4Vv6v3DAAC32IsiWS7g.gif?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/Y4Vv6v3DAAC32IsiWS7g.gif?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/Y4Vv6v3DAAC32IsiWS7g.gif?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/Y4Vv6v3DAAC32IsiWS7g.gif?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/Y4Vv6v3DAAC32IsiWS7g.gif?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/Y4Vv6v3DAAC32IsiWS7g.gif?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/Y4Vv6v3DAAC32IsiWS7g.gif?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/Y4Vv6v3DAAC32IsiWS7g.gif?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/Y4Vv6v3DAAC32IsiWS7g.gif?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/Y4Vv6v3DAAC32IsiWS7g.gif?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/Y4Vv6v3DAAC32IsiWS7g.gif?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/Y4Vv6v3DAAC32IsiWS7g.gif?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/Y4Vv6v3DAAC32IsiWS7g.gif?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/Y4Vv6v3DAAC32IsiWS7g.gif?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/Y4Vv6v3DAAC32IsiWS7g.gif?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/Y4Vv6v3DAAC32IsiWS7g.gif?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;h2 id=&quot;check-out-the-spec&quot;&gt;Check out the spec &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/hands-on-portals/#check-out-the-spec&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We are actively discussing
&lt;a href=&quot;https://wicg.github.io/portals/&quot; rel=&quot;noopener&quot;&gt;the Portals spec&lt;/a&gt; in the Web Incubation Community Group (WICG).
To quickly get up to speed, take a look at some of the
&lt;a href=&quot;https://github.com/WICG/portals/blob/master/key-scenarios.md&quot; rel=&quot;noopener&quot;&gt;key scenarios&lt;/a&gt;.
These are the three important features to familiarize yourself with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://wicg.github.io/portals/#the-portal-element&quot; rel=&quot;noopener&quot;&gt;The &lt;code&gt;&amp;lt;portal&amp;gt;&lt;/code&gt; element:&lt;/a&gt; The HTML element itself. The API is very simple. It consists of the &lt;code&gt;src&lt;/code&gt; attribute, the &lt;code&gt;activate&lt;/code&gt; function and an interface for messaging (&lt;code&gt;postMessage&lt;/code&gt;). &lt;code&gt;activate&lt;/code&gt; takes an optional argument to pass data to the &lt;code&gt;&amp;lt;portal&amp;gt;&lt;/code&gt; upon activation.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wicg.github.io/portals/#the-portalhost-interface&quot; rel=&quot;noopener&quot;&gt;The &lt;code&gt;portalHost&lt;/code&gt; interface:&lt;/a&gt; Adds a &lt;code&gt;portalHost&lt;/code&gt; object to the &lt;code&gt;window&lt;/code&gt; object. This lets you check if the page is embedded as a &lt;code&gt;&amp;lt;portal&amp;gt;&lt;/code&gt; element. It also provides an interface for messaging (&lt;code&gt;postMessage&lt;/code&gt;) back to the host.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wicg.github.io/portals/#the-portalactivateevent-interface&quot; rel=&quot;noopener&quot;&gt;The PortalActivateEvent interface:&lt;/a&gt; An event that fires when the &lt;code&gt;&amp;lt;portal&amp;gt;&lt;/code&gt; is activated. There is a neat function called &lt;code&gt;adoptPredecessor&lt;/code&gt; which you can use to retrieve the previous page as a &lt;code&gt;&amp;lt;portal&amp;gt;&lt;/code&gt; element. This allows you to create seamless navigations and composed experiences between two pages.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let&#39;s look beyond the basic usage pattern. Here is a non-exhaustive list of what you can achieve with Portals along with sample code.&lt;/p&gt;
&lt;h3 id=&quot;customize-the-style-when-embedded-as-a-lessportalgreater-element&quot;&gt;Customize the style when embedded as a &lt;code&gt;&amp;lt;portal&amp;gt;&lt;/code&gt; element &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/hands-on-portals/#customize-the-style-when-embedded-as-a-lessportalgreater-element&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Detect whether this page is hosted in a portal&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;portalHost&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Customize the UI when being embedded as a portal&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;messaging-between-the-lessportalgreater-element-and-portalhost&quot;&gt;Messaging between the &lt;code&gt;&amp;lt;portal&amp;gt;&lt;/code&gt; element and &lt;code&gt;portalHost&lt;/code&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/hands-on-portals/#messaging-between-the-lessportalgreater-element-and-portalhost&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Send message to the portal element&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; portal &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;portal&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;portal&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;postMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;someKey&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; someValue&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;ORIGIN&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Receive message via window.portalHost&lt;/span&gt;&lt;br /&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;portalHost&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;message&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;evt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; evt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;someKey&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// handle the event&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;activating-the-lessportalgreater-element-and-receiving-the-portalactivate-event&quot;&gt;Activating the &lt;code&gt;&amp;lt;portal&amp;gt;&lt;/code&gt; element and receiving the &lt;code&gt;portalactivate&lt;/code&gt; event &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/hands-on-portals/#activating-the-lessportalgreater-element-and-receiving-the-portalactivate-event&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// You can optionally add data to the argument of the activate function&lt;/span&gt;&lt;br /&gt;portal&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;activate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;somekey&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;somevalue&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// The portal content will receive the portalactivate event&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// when the activate happens&lt;/span&gt;&lt;br /&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;portalactivate&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;evt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Data available as evt.data&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; evt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;retrieving-the-predecessor&quot;&gt;Retrieving the predecessor &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/hands-on-portals/#retrieving-the-predecessor&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Listen to the portalactivate event&lt;/span&gt;&lt;br /&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;portalactivate&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;evt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// ... and creatively use the predecessor&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; portal &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; evt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;adoptPredecessor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;someElm&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendChild&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;portal&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;knowing-your-page-was-adopted-as-a-predecessor&quot;&gt;Knowing your page was adopted as a predecessor &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/hands-on-portals/#knowing-your-page-was-adopted-as-a-predecessor&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// The activate function returns a Promise.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// When the promise resolves, it means that the portal has been activated.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// If this document was adopted by it, then window.portalHost will exist.&lt;/span&gt;&lt;br /&gt;portal&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;activate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Check if this document was adopted into a portal element.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;portalHost&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// You can start communicating with the portal element&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// i.e. listen to messages&lt;/span&gt;&lt;br /&gt;    window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;portalHost&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;message&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;evt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token comment&quot;&gt;// handle the event&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;By combining all of the features supported by Portals,
you can build really fancy user experiences.
For instance, the demo below demonstrates how Portals can enable a seamless user experience
between a website and third party embed content.&lt;/p&gt;
&lt;div class=&quot;youtube&quot;&gt;  &lt;lite-youtube videoid=&quot;4JkipxFVE9k&quot;&gt;  &lt;/lite-youtube&gt;&lt;/div&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; Interested in this demo? &lt;a href=&quot;https://github.com/WICG/portals/tree/master/demos/portal-embed-demo&quot;&gt;Fork it on GitHub&lt;/a&gt; and build your own version! &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;use-cases-and-plans&quot;&gt;Use cases and plans &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/hands-on-portals/#use-cases-and-plans&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We hope you liked this brief tour of Portals! We can&#39;t wait to see what you can come up with. For instance, you might want to start using Portals for non-trivial navigations such as: pre-rendering the page for your best-seller product from a product category listing page.&lt;/p&gt;
&lt;p&gt;Another important thing to know is that Portals can be used in cross-origin navigations, just like an &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt;. So, if you have multiple websites that cross reference one another, you can also use Portals to create seamless navigations between two different websites. This cross-origin use case is very unique to Portals, and can even improve the user experience of SPAs.&lt;/p&gt;
&lt;h2 id=&quot;feedback-welcome&quot;&gt;Feedback welcome &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/hands-on-portals/#feedback-welcome&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Portals are ready for experimentation in Chrome 85 and later versions. Feedback from the community is crucial to the design of new APIs, so please try it out and tell us what you think! If you have any feature requests or feedback, please head over to the &lt;a href=&quot;https://github.com/WICG/portals/issues&quot; rel=&quot;noopener&quot;&gt;WICG GitHub repo&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Yusuke Utsunomiya</name>
    </author>
  </entry>
</feed>
