![Barry Pollard](https://web.dev/images/authors/tunetheweb.jpg) Barry Pollard [X](https://twitter.com/tunetheweb) [GitHub](https://github.com/tunetheweb) [Mastodon](https://mastodon.social/@tunetheweb) [Bluesky](https://bsky.app/profile/tunetheweb.com) [Homepage](https://www.tunetheweb.com) ![Michal Mocny](https://web.dev/images/authors/mmocny.jpg) Michal Mocny [X](https://twitter.com/mmocny) [GitHub](https://github.com/mmocny) [Bluesky](https://bsky.app/profile/https://bsky.app/profile/mmocny.com)

<br />


Published: February 1, 2023, Last updated: September 20, 2026

<br />


Browser Support

- ![Chrome: 151.](data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='-10 -10 276 276'%3E%3ClinearGradient id='a' x1='145' x2='34' y1='253' y2='61' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%231e8e3e'/%3E%3Cstop offset='1' stop-color='%2334a853'/%3E%3C/linearGradient%3E%3ClinearGradient id='b' x1='111' x2='222' y1='254' y2='62' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23fcc934'/%3E%3Cstop offset='1' stop-color='%23fbbc04'/%3E%3C/linearGradient%3E%3ClinearGradient id='c' x1='17' x2='239' y1='80' y2='80' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23d93025'/%3E%3Cstop offset='1' stop-color='%23ea4335'/%3E%3C/linearGradient%3E%3Ccircle cx='128' cy='128' r='64' fill='%23fff'/%3E%3Cpath fill='url(%23a)' d='M96 183a64 64 0 0 1-23-23L17 64a128 128 0 0 0 111 192l55-96a64 64 0 0 1-87 23Z'/%3E%3Cpath fill='url(%23b)' d='M192 128a64 64 0 0 1-9 32l-55 96A128 128 0 0 0 239 64H128a64 64 0 0 1 64 64Z'/%3E%3Ccircle cx='128' cy='128' r='52' fill='%231a73e8'/%3E%3Cpath fill='url(%23c)' d='M96 73a64 64 0 0 1 32-9h111a128 128 0 0 0-222 0l56 96a64 64 0 0 1 23-87Z'/%3E%3C/svg%3E)
- ![Edge: 151.](data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='24' height='24' viewBox='0 0 27600 27600'%3E%3ClinearGradient id='A' gradientUnits='userSpaceOnUse'/%3E%3ClinearGradient id='B' x1='6870' x2='24704' y1='18705' y2='18705' xlink:href='%23A'%3E%3Cstop offset='0' stop-color='%230c59a4'/%3E%3Cstop offset='1' stop-color='%23114a8b'/%3E%3C/linearGradient%3E%3ClinearGradient id='C' x1='16272' x2='5133' y1='10968' y2='23102' xlink:href='%23A'%3E%3Cstop offset='0' stop-color='%231b9de2'/%3E%3Cstop offset='.16' stop-color='%231595df'/%3E%3Cstop offset='.67' stop-color='%230680d7'/%3E%3Cstop offset='1' stop-color='%230078d4'/%3E%3C/linearGradient%3E%3CradialGradient id='D' cx='16720' cy='18747' r='9538' xlink:href='%23A'%3E%3Cstop offset='.72' stop-opacity='0'/%3E%3Cstop offset='.95' stop-opacity='.53'/%3E%3Cstop offset='1'/%3E%3C/radialGradient%3E%3CradialGradient id='E' cx='7130' cy='19866' r='14324' gradientTransform='matrix(.14843 -.98892 .79688 .1196 -8759 25542)' xlink:href='%23A'%3E%3Cstop offset='.76' stop-opacity='0'/%3E%3Cstop offset='.95' stop-opacity='.5'/%3E%3Cstop offset='1'/%3E%3C/radialGradient%3E%3CradialGradient id='F' cx='2523' cy='4680' r='20243' gradientTransform='matrix(-.03715 .99931 -2.12836 -.07913 13579 3530)' xlink:href='%23A'%3E%3Cstop offset='0' stop-color='%2335c1f1'/%3E%3Cstop offset='.11' stop-color='%2334c1ed'/%3E%3Cstop offset='.23' stop-color='%232fc2df'/%3E%3Cstop offset='.31' stop-color='%232bc3d2'/%3E%3Cstop offset='.67' stop-color='%2336c752'/%3E%3C/radialGradient%3E%3CradialGradient id='G' cx='24247' cy='7758' r='9734' gradientTransform='matrix(.28109 .95968 -.78353 .22949 24510 -16292)' xlink:href='%23A'%3E%3Cstop offset='0' stop-color='%2366eb6e'/%3E%3Cstop offset='1' stop-color='%2366eb6e' stop-opacity='0'/%3E%3C/radialGradient%3E%3Cpath id='H' d='M24105 20053a9345 9345 0 01-1053 472 10202 10202 0 01-3590 646c-4732 0-8855-3255-8855-7432 0-1175 680-2193 1643-2729-4280 180-5380 4640-5380 7253 0 7387 6810 8137 8276 8137 791 0 1984-230 2704-456l130-44a12834 12834 0 006660-5282c220-350-168-757-535-565z'/%3E%3Cpath id='I' d='M11571 25141a7913 7913 0 01-2273-2137 8145 8145 0 01-1514-4740 8093 8093 0 013093-6395 8082 8082 0 011373-859c312-148 846-414 1554-404a3236 3236 0 012569 1297 3184 3184 0 01636 1866c0-21 2446-7960-8005-7960-4390 0-8004 4166-8004 7820 0 2319 538 4170 1212 5604a12833 12833 0 007684 6757 12795 12795 0 003908 610c1414 0 2774-233 4045-656a7575 7575 0 01-6278-803z'/%3E%3Cpath id='J' d='M16231 15886c-80 105-330 250-330 566 0 260 170 512 472 723 1438 1003 4149 868 4156 868a5954 5954 0 003027-839 6147 6147 0 001133-850 6180 6180 0 001910-4437c26-2242-796-3732-1133-4392-2120-4141-6694-6525-11668-6525-7011 0-12703 5635-12798 12620 47-3654 3679-6605 7996-6605 350 0 2346 34 4200 1007 1634 858 2490 1894 3086 2921 618 1067 728 2415 728 2952s-271 1333-780 1990z'/%3E%3Cuse fill='url(%23B)' xlink:href='%23H'/%3E%3Cuse fill='url(%23D)' opacity='.35' xlink:href='%23H'/%3E%3Cuse fill='url(%23C)' xlink:href='%23I'/%3E%3Cuse fill='url(%23E)' opacity='.4' xlink:href='%23I'/%3E%3Cuse fill='url(%23F)' xlink:href='%23J'/%3E%3Cuse fill='url(%23G)' xlink:href='%23J'/%3E%3C/svg%3E)
- ![Firefox: not supported.](data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 512 512'%3E%3Cdefs%3E%3CradialGradient id='ff-b' cx='428.5' cy='55.1' r='501' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23ffbd4f'/%3E%3Cstop offset='.2' stop-color='%23ffac31'/%3E%3Cstop offset='.3' stop-color='%23ff9d17'/%3E%3Cstop offset='.3' stop-color='%23ff980e'/%3E%3Cstop offset='.4' stop-color='%23ff563b'/%3E%3Cstop offset='.5' stop-color='%23ff3750'/%3E%3Cstop offset='.7' stop-color='%23f5156c'/%3E%3Cstop offset='.8' stop-color='%23eb0878'/%3E%3Cstop offset='.9' stop-color='%23e50080'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-c' cx='245.4' cy='259.9' r='501' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.3' stop-color='%23960e18'/%3E%3Cstop offset='.3' stop-color='%23b11927' stop-opacity='.7'/%3E%3Cstop offset='.4' stop-color='%23db293d' stop-opacity='.3'/%3E%3Cstop offset='.5' stop-color='%23f5334b' stop-opacity='.1'/%3E%3Cstop offset='.5' stop-color='%23ff3750' stop-opacity='0'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-d' cx='305.8' cy='-58.6' r='363' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23fff44f'/%3E%3Cstop offset='.3' stop-color='%23ffdc3e'/%3E%3Cstop offset='.5' stop-color='%23ff9d12'/%3E%3Cstop offset='.5' stop-color='%23ff980e'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-e' cx='190' cy='390.8' r='238.6' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.3' stop-color='%233a8ee6'/%3E%3Cstop offset='.5' stop-color='%235c79f0'/%3E%3Cstop offset='.7' stop-color='%239059ff'/%3E%3Cstop offset='1' stop-color='%23c139e6'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-f' cx='252.2' cy='201.3' r='126.5' gradientTransform='matrix(1 0 0 1 -48 31)' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.2' stop-color='%239059ff' stop-opacity='0'/%3E%3Cstop offset='.3' stop-color='%238c4ff3' stop-opacity='.1'/%3E%3Cstop offset='.8' stop-color='%237716a8' stop-opacity='.5'/%3E%3Cstop offset='1' stop-color='%236e008b' stop-opacity='.6'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-g' cx='239.1' cy='34.6' r='171.6' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23ffe226'/%3E%3Cstop offset='.1' stop-color='%23ffdb27'/%3E%3Cstop offset='.3' stop-color='%23ffc82a'/%3E%3Cstop offset='.5' stop-color='%23ffa930'/%3E%3Cstop offset='.7' stop-color='%23ff7e37'/%3E%3Cstop offset='.8' stop-color='%23ff7139'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-h' cx='374' cy='-74.3' r='732.2' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23fff44f'/%3E%3Cstop offset='.5' stop-color='%23ff980e'/%3E%3Cstop offset='.6' stop-color='%23ff5634'/%3E%3Cstop offset='.7' stop-color='%23ff3647'/%3E%3Cstop offset='.9' stop-color='%23e31587'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-i' cx='304.6' cy='7.1' r='536.4' gradientTransform='rotate(84 303 4)' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23fff44f'/%3E%3Cstop offset='.1' stop-color='%23ffe847'/%3E%3Cstop offset='.2' stop-color='%23ffc830'/%3E%3Cstop offset='.3' stop-color='%23ff980e'/%3E%3Cstop offset='.4' stop-color='%23ff8b16'/%3E%3Cstop offset='.5' stop-color='%23ff672a'/%3E%3Cstop offset='.6' stop-color='%23ff3647'/%3E%3Cstop offset='.7' stop-color='%23e31587'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-j' cx='235' cy='98.1' r='457.1' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23fff44f'/%3E%3Cstop offset='.5' stop-color='%23ff980e'/%3E%3Cstop offset='.6' stop-color='%23ff5634'/%3E%3Cstop offset='.7' stop-color='%23ff3647'/%3E%3Cstop offset='.9' stop-color='%23e31587'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-k' cx='355.7' cy='124.9' r='500.3' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23fff44f'/%3E%3Cstop offset='.2' stop-color='%23ffe141'/%3E%3Cstop offset='.5' stop-color='%23ffaf1e'/%3E%3Cstop offset='.6' stop-color='%23ff980e'/%3E%3C/radialGradient%3E%3ClinearGradient id='ff-a' x1='446.9' y1='76.8' x2='47.9' y2='461.8' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23fff44f'/%3E%3Cstop offset='.1' stop-color='%23ffe847'/%3E%3Cstop offset='.2' stop-color='%23ffc830'/%3E%3Cstop offset='.4' stop-color='%23ff980e'/%3E%3Cstop offset='.4' stop-color='%23ff8b16'/%3E%3Cstop offset='.5' stop-color='%23ff672a'/%3E%3Cstop offset='.5' stop-color='%23ff3647'/%3E%3Cstop offset='.7' stop-color='%23e31587'/%3E%3C/linearGradient%3E%3ClinearGradient id='ff-l' x1='442.1' y1='74.8' x2='102.6' y2='414.3' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.2' stop-color='%23fff44f' stop-opacity='.8'/%3E%3Cstop offset='.3' stop-color='%23fff44f' stop-opacity='.6'/%3E%3Cstop offset='.5' stop-color='%23fff44f' stop-opacity='.2'/%3E%3Cstop offset='.6' stop-color='%23fff44f' stop-opacity='0'/%3E%3C/linearGradient%3E%3C/defs%3E%3Cpath d='M479 166c-11-25-32-52-49-60a249 249 0 0 1 25 73c-27-68-73-95-111-155a255 255 0 0 1-8-14 44 44 0 0 1-4-9 1 1 0 0 0 0-1 1 1 0 0 0-1 0c-60 35-81 101-83 134a120 120 0 0 0-66 25 71 71 0 0 0-6-5 111 111 0 0 1-1-58c-25 11-44 29-58 44-9-12-9-52-8-60l-8 4a175 175 0 0 0-24 21 210 210 0 0 0-22 26 203 203 0 0 0-32 73l-1 2-2 15a229 229 0 0 0-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121zM202 355l3 1-3-1zm55-145zm198-31z' fill='url(%23ff-a)'/%3E%3Cpath d='M479 166c-11-25-32-52-49-60 14 26 22 53 25 72v1a207 207 0 0 1-206 279c-113-3-212-87-231-197-3-17 0-26 2-40-2 11-3 14-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121z' fill='url(%23ff-b)'/%3E%3Cpath d='M479 166c-11-25-32-52-49-60 14 26 22 53 25 72v1a207 207 0 0 1-206 279c-113-3-212-87-231-197-3-17 0-26 2-40-2 11-3 14-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121z' fill='url(%23ff-c)'/%3E%3Cpath d='m362 195 1 1a130 130 0 0 0-22-29C266 92 322 5 331 0c-60 35-81 101-83 134l9-1c45 0 84 25 105 62z' fill='url(%23ff-d)'/%3E%3Cpath d='M257 210c-1 6-22 26-29 26-68 0-80 41-80 41 3 35 28 64 57 79l4 2 7 3a107 107 0 0 0 31 6c120 6 143-143 57-186 22-4 45 5 58 14-21-37-60-62-105-62l-9 1a120 120 0 0 0-66 25l17 16c16 16 58 33 58 35z' fill='url(%23ff-e)'/%3E%3Cpath d='M257 210c-1 6-22 26-29 26-68 0-80 41-80 41 3 35 28 64 57 79l4 2 7 3a107 107 0 0 0 31 6c120 6 143-143 57-186 22-4 45 5 58 14-21-37-60-62-105-62l-9 1a120 120 0 0 0-66 25l17 16c16 16 58 33 58 35z' fill='url(%23ff-f)'/%3E%3Cpath d='m171 151 5 3a111 111 0 0 1-1-58c-25 11-44 29-58 44 1 0 36 0 54 11z' fill='url(%23ff-g)'/%3E%3Cpath d='M18 261a242 242 0 0 0 231 197 207 207 0 0 0 206-279c8 56-20 110-64 146-86 71-169 43-186 31l-3-1c-50-24-71-70-67-110-42 0-57-35-57-35s38-28 89-4c46 22 90 4 90 4 0-2-42-19-58-35l-17-16a71 71 0 0 0-6-5l-5-3c-18-11-52-11-54-11-9-12-9-51-8-60l-8 4a175 175 0 0 0-24 21 210 210 0 0 0-22 26 203 203 0 0 0-32 73c0 1-9 38-5 57z' fill='url(%23ff-h)'/%3E%3Cpath d='M341 167a130 130 0 0 1 22 29 46 46 0 0 1 4 3c55 50 26 121 24 126 44-36 72-90 64-146-27-68-73-95-111-155a255 255 0 0 1-8-14 44 44 0 0 1-4-9 1 1 0 0 0 0-1 1 1 0 0 0-1 0c-9 5-65 92 10 167z' fill='url(%23ff-i)'/%3E%3Cpath d='M367 199a46 46 0 0 0-4-3l-1-1c-13-9-36-18-58-15 86 44 63 193-57 187a107 107 0 0 1-31-6 131 131 0 0 1-11-5c17 12 99 39 186-31 2-5 31-76-24-126z' fill='url(%23ff-j)'/%3E%3Cpath d='M148 277s12-41 80-41c7 0 28-20 29-26s-44 18-90-4c-51-24-89 4-89 4s15 35 57 35c-4 40 16 85 67 110l3 1c-29-15-54-44-57-79z' fill='url(%23ff-k)'/%3E%3Cpath d='M479 166c-11-25-32-52-49-60a249 249 0 0 1 25 73c-27-68-73-95-111-155a255 255 0 0 1-8-14 44 44 0 0 1-4-9 1 1 0 0 0 0-1 1 1 0 0 0-1 0c-60 35-81 101-83 134l9-1c45 0 84 25 105 62-13-9-36-18-58-14 86 43 63 192-57 186a107 107 0 0 1-31-6 131 131 0 0 1-11-5l-3-1 3 1c-29-15-54-44-57-79 0 0 12-41 80-41 7 0 28-20 29-26 0-2-42-19-58-35l-17-16a71 71 0 0 0-6-5 111 111 0 0 1-1-58c-25 11-44 29-58 44-9-12-9-52-8-60l-8 4a175 175 0 0 0-24 21 210 210 0 0 0-22 26 203 203 0 0 0-32 73l-1 2-2 15a279 279 0 0 0-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121zm-24 13z' fill='url(%23ff-l)'/%3E%3C/svg%3E)
- ![Safari: not supported.](data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='24' height='24' viewBox='195 190 135 135'%3E%3Cdefs%3E%3ClinearGradient id='s-a' x1='132.6' x2='134.4' y1='111.7' y2='-105.3' xlink:href='%23s-b'%3E%3Cstop offset='0' stop-color='%23d2d2d2' /%3E%3Cstop offset='.5' stop-color='%23f2f2f2' /%3E%3Cstop offset='1' stop-color='%23fff' /%3E%3C/linearGradient%3E%3ClinearGradient id='s-b' gradientUnits='userSpaceOnUse' /%3E%3ClinearGradient id='s-c' x1='65.4' x2='67.4' y1='115.7' y2='17.1' xlink:href='%23s-b'%3E%3Cstop offset='0' stop-color='%23005ad5' /%3E%3Cstop offset='.2' stop-color='%230875f0' /%3E%3Cstop offset='.3' stop-color='%23218cee' /%3E%3Cstop offset='.6' stop-color='%2327a5f3' /%3E%3Cstop offset='.8' stop-color='%2325aaf2' /%3E%3Cstop offset='1' stop-color='%2321aaef' /%3E%3C/linearGradient%3E%3ClinearGradient id='s-d' x1='158.7' x2='176.3' y1='96.7' y2='79.5' xlink:href='%23s-b'%3E%3Cstop offset='0' stop-color='%23c72e24' /%3E%3Cstop offset='1' stop-color='%23fd3b2f' /%3E%3C/linearGradient%3E%3CradialGradient id='s-i' cx='-69.9' cy='69.3' r='54' gradientTransform='matrix(.9 -.01 .04 2.72 -9 -120)' xlink:href='%23s-b'%3E%3Cstop offset='0' stop-color='%2324a5f3' stop-opacity='0' /%3E%3Cstop offset='1' stop-color='%231e8ceb' /%3E%3C/radialGradient%3E%3CradialGradient id='s-j' cx='109.3' cy='13.8' r='93.1' gradientTransform='matrix(-.02 1.1 -1.04 -.02 137 -115)' xlink:href='%23s-b'%3E%3Cstop offset='0' stop-opacity='0' /%3E%3Cstop offset='1' stop-color='%235488d6' stop-opacity='0' /%3E%3Cstop offset='1' stop-color='%235d96eb' /%3E%3C/radialGradient%3E%3C/defs%3E%3Crect width='220' height='220' x='22' y='-107' fill='url(%23s-a)' ry='49' transform='matrix(.57 0 0 .57 187 256)' /%3E%3Cg transform='translate(194 190)'%3E%3Ccircle cx='67.8' cy='67.7' fill='url(%23s-c)' paint-order='stroke fill markers' r='54' /%3E%3Ccircle cx='-69.9' cy='69.3' fill='url(%23s-i)' transform='translate(138 -2)' r='54' /%3E%3C/g%3E%3Cellipse cx='120' cy='14.2' fill='url(%23s-j)' rx='93.1' ry='93.7' transform='matrix(.58 0 0 .58 192 250)' /%3E%3Cg transform='matrix(.58 0 0 .57 197 182)'%3E%3Cpath fill='%23cac7c8' d='M46 192h1l72-48-7-9-66 57Z' /%3E%3Cpath fill='%23fbfffc' d='M46 191v1l66-57-7-9-59 65Z' /%3E%3Cpath fill='url(%23s-d)' d='m119 144-7-9 66-57-59 66Z' /%3E%3Cpath fill='%23fb645c' d='m105 126 7 9 66-57-1-1-72 49Z' /%3E%3C/g%3E%3Cpath stroke='%23fff' stroke-linecap='round' stroke-miterlimit='1' stroke-width='1.3' d='m287 278 3-2m-12-17 8-2m-8-3h4m-4-13 8 2m-8 3h4m-1-13 7 3m-4-11 7 4m-2-11 6 6m0-12 6 7m1-11 4 6m4-10 3 7m5-9 2 7m15-7-1 7m10-5-3 7m11-4-4 7m11-2-5 6m16 7-7 4m10 4-7 3m10 6-8 1m8 16-8-2m5 10-7-3m4 11-7-4m2 11-6-5m0 11-5-6m-2 11-4-7m-4 11-3-8m-6 10-1-8m-16 8 2-8m-10 5 3-7m-11 4 4-7m-11 2 5-6m-8 3 3-3m4 8 2-3m5 8 2-4m6 7 1-4m8 5v-4m8 4v-4m9 3-1-4m9 1-2-4m9 0-2-4m9-2-3-3m8-4-3-2m8-5-4-2m7-6-4-1m5-8h-4m4-8h-4m3-9-4 1m1-9-4 2m-1-9-3 2m-2-9-3 3m-4-8-2 3m-5-8-2 4m-6-6-1 3m-8-5v4m-8-4v4m-9-2 1 3m-9 0 2 3m-9 1 2 3m-9 2 3 3m-8 4 3 2m-8 5 4 2m-7 6 4 1m-4 25 4-1m-2 5 7-3m-6 7 4-2m-2 6 7-4m-13-21h8m41-41v-8m0 99v-8m49-42h-8' transform='translate(-65 8)' /%3E%3C/svg%3E)

[Source](https://developer.mozilla.org/docs/Web/API/PerformanceSoftNavigation)

<br />

Since its launch, the [Core Web Vitals initiative](https://web.dev/articles/vitals) has sought to measure the actual user experience of a website, rather than technical details behind how a website is created or loaded. The three Core Web Vitals metrics were created as [user-centric metrics](https://web.dev/articles/user-centric-performance-metrics)---an evolution over existing technical metrics such as[`DOMContentLoaded`](https://developer.mozilla.org/docs/Web/API/Document/DOMContentLoaded_event) or [`load`](https://developer.mozilla.org/docs/Web/API/Window/load_event) that measured timings that were often unrelated to how users perceived the performance of the page. Because of this, the technology used to build the site shouldn't impact the scoring providing the site performs well.

The reality is always a little trickier than the ideal, and the popular Single Page Application architecture has [never been fully supported by the Core Web Vitals metrics](https://web.dev/articles/vitals-spa-faq). Rather than loading distinct, individual web pages as the user navigates about the site, these web applications use so-called "soft navigations", where the page content is instead changed by JavaScript. In these applications, the illusion of a conventional web page architecture is maintained by altering the URL and pushing previous URLs in the browser's history to allow the back and forward buttons to work as the user would expect.

Many JavaScript frameworks use this model, but each in a different way. Since this is outside of what the browser traditionally understands as a "page", measuring this has always been difficult: where is the line to be drawn between an interaction on the *current* page, versus considering this as a *new* page?

The Chrome team has considered this challenge for some time now, and is looking to standardize a definition of what is a "soft-navigation", and how the Core Web Vitals can be measured for this---in a similar way that websites implemented in the conventional multi-page architecture (MPA) are measured.

We've made several improvements to the proposal based on developer feedback and have launched two new performance APIs to help solve this issue from Chrome 151. Additionally, we have made the soft navigation metrics available in the Chrome User Experience Report (CrUX) as experimental metrics.

## What is a soft navigation?

We have come up with the following definition of a *soft navigation*:

- The navigation is initiated by a user action.
- The navigation results in a visible URL change to the user.
- The interaction results in a visible paint.

For some sites, this definition may lead to false positives (that users wouldn't really consider a "navigation" to have happened) or false negatives (where the user does consider a "navigation" to have happened despite not meeting these criteria). We welcome feedback at [the soft navigation specification repository](https://github.com/WICG/soft-navigations/issues).

## DevTools support for soft navigations

We have added support for soft navigations to the DevTools **Performance panel** in **Live Metrics** view, and in the trace view with **Insights** and marker support:
DevTools support for soft navigations.

## How does Chrome implement soft navigations for web developers?

Once the soft navigation feature is enabled (more on this in the next section), Chrome will change the way it reports some performance metrics:

- A `soft-navigation` [`PerformanceTiming`](https://developer.mozilla.org/docs/Web/API/PerformanceTiming) event will be emitted after each soft navigation is detected.
- This `soft-navigation` entry will include a `navigationId`, the new URL in the `name` attribute, as well as an `interactionId` of the initiating interaction.
- One or more `interaction-contentful-paint` entries will be emitted after interactions that cause a contentful paint. This will contain a `largestContentfulPaint` entry that can be used to measure [Largest Contentful Paint (LCP)](https://web.dev/articles/lcp) for soft navigations.
- The `navigationId` attribute is added to each of performance timings (`first-paint`, `first-contentful-paint`, `largest-contentful-paint`, `interaction-contentful-paint`, `first-input-delay`, `event`, and `layout-shift`). This corresponds to the navigation entry the event was emitted under. Note that when these entries span soft navigations, they may contain the previous or next `navigationId` depending on when the entry was emitted. More on this in the [Report the metrics against the appropriate URL](https://developer.chrome.com/docs/web-platform/soft-navigations#metrics-url) section.
- The `soft-navigation` will include a `getLargestInteractionContentfulPaint()` function to retrieve the largest `interaction-contentful-paint` entry for that navigation. This can then be used as the initial LCP for that navigation, and that LCP can then be updated as more `interaction-contentful-paint` entries for that interaction are observed. *Note this replaces a `largestInteractionContentfulPaint` attribute available in previous origin trials.*
- Potentially some `interaction-contentful-paint` entries may have happened before the soft navigation takes place (if the URL update does not happen until after those paints). In these cases the `getLargestInteractionContentfulPaint()` function avoids needing to buffer and look back over old entries after a soft navigation is finalized. Note that the entry returned by `getLargestInteractionContentfulPaint()` is an exact copy of the largest `interaction-contentful-paint` entry at the time it was emitted, so that entry may have used the previous `navigationId` since that is when the paint happened, but these paints should be measured against the new `navigationId`.
- The `soft-navigation` entry will also include `paintTime` and `presentationTime` as the FCP for that navigation.
- Be aware that `interaction-contentful-paint` entries will also be emitted after further interactions, but LCP for a URL should be restricted to `interaction-contentful-paint` entries that match the soft navigations `interactionId` to exclude these and also only to `largestContentfulPaint` properties within that.

These changes will allow the Core Web Vitals---and some of the associated diagnostic metrics---to be measured per page navigation, though there are some nuances that need to be considered.

### What are the implications of enabling soft navigations in Chrome?

The following are some of the changes that sites owners need to consider after enabling this feature:

- Monitoring the `soft-navigation` entries allows "slicing" of the performance entries into each "navigation".
- CLS and INP metrics can already be sliced at your discretion, rather than be measured over the duration of the whole page lifecycle, but the Soft Navigation feature gives a standardized measure of when this happens, regardless of underlying technology used.
- The `largest-contentful-paint` entry is finalized on an interaction (which is necessary to start a soft navigation), so it can only be used to measure for the initial "hard" navigation LCP. This means this won't change when soft navigations are measured so LCP for the initial, hard navigation, page load can be measured as it always was.
- The new `interaction-contentful-paint` entry that will be emitted from interactions can be used to measure LCP for soft navigations by looking at the `largestContentfulPaint` property within that, but there are some considerations for how to use this entry we will discuss in thie article.
- Note that not all users will support this soft navigation feature, particularly for those using other browsers or versions of Chrome before 151. Be aware that some users may not report soft-navigation-based metrics, even if they report Core Web Vitals metrics.

Check with your RUM provider if they support measuring Core Web Vitals by soft navigation. Many are planning on testing this new standard, and will take the previous considerations into account. In the meantime, some providers also allow limited measurements of performance metrics based on their own heuristics.

For more information on how to measure the metrics for soft navigations, see [the Measuring Core Web Vitals per soft navigation section](https://developer.chrome.com/docs/web-platform/soft-navigations#measure-core-web-vitals).

### How do I enable soft navigations in Chrome?

The soft navigations feature is enabled by default from Chrome 151.

### Feature detecting Soft Navigations API support

You can use the following code to test if the API is supported:

    if (PerformanceObserver.supportedEntryTypes.includes('soft-navigation')) {
      // Monitor Soft Navigations
    }

Or alternatively:

    if ('SoftNavigationEntry' in window) {
      // Monitor Soft Navigations
    }

## How can I measure soft navigations?

Where supported, the metrics can be reporting using the [`PerformanceObserver`](https://developer.mozilla.org/docs/Web/API/PerformanceObserver) API as per other metrics. However, there are some extra considerations that need to be taken into account for these metrics.

### Report soft navigations

You can use a `PerformanceObserver` to observe soft navigations. Following is an example code snippet that logs soft navigation entries to the console---including previous soft navigations on this page using the `buffered` option:

    const observer = new PerformanceObserver(console.log);
    observer.observe({ type: "soft-navigation", buffered: true });

This can be used to finalize full-life page metrics for the previous navigation.

### Report the metrics against the appropriate URL

When a soft navigations is seen, the previous page's Core Web Vitals should be finalized, and then reported for the previous URL, and new monitoring should be started for the new URL.

The `name` attribute of the appropriate `soft-navigation` entry will contain the new URL to report metrics for, and the `navigationId` will be the unique reference for this navigation (since the same URL may be visited multiple times over the life of a single page application).

This should be set as each `soft-navigation` entry and used to report metrics until the next `soft-navigation` entry is received.

> [!WARNING]
> **Warning:** It is possible to look up the given `soft-navigation` entry using `getEntriesByType` but this is limited to the first 50 buffered entries so using a performance observer to monitor all the entries as they are received is the recommended method, and is necessary for long-lived apps over more than 50 soft navigations.

#### Report the correct URL for `interaction-contentful-paint`

Extra considerations are needed to calculating LCP from `interaction-contentful-paint` entries, since not all `interaction-contentful-paint` entries should be mapped using the `navigationId` and reported as LCP for that URL:

- The first issue is that `interaction-contentful-paint` entries may be emitted before the soft navigation occurs if a paint occurs before the URL update. In these cases the `navigationId` will be for the old URL. If the URL is updated first, then the paint will complete the soft navigation and in that case the `soft-navigation` entry will be emitted first, and the `interaction-contentful-paint` will have the new URL.
- The second issue is that `interaction-contentful-paint`, entries will continue to be emitted for newer interactions, as the scope of this performance metric extends beyond just LCP for soft navigations. We only want to consider the paints for the soft navigation load for LCP and not those for subsequent interactions.

Therefore, the `interactionId` rather than the `navigationId` should be used to map `interaction-contentful-paint` entries to `soft-navigation-entries` to obtain the correct URL. This will handle any entries with old `navigationId`s as well as filter out any `interaction-contentful-paint` entries that shouldn't be considered for LCP.

Additionally, you should process the `getLargestInteractionContentfulPaint()` function of the `soft-navigation` entries first, to handle `interaction-contentful-paint` entries that occurred before the `soft-navigation entries` is emitted.

### Getting the `startTime` of soft navigations

All performance timings, including those for soft navigations, and the entries used to calculate the Core Web Vitals metrics are reported as a time from the initial "hard" page navigation time. Therefore, the soft navigation start time should be subtracted from soft navigation loading metric times (for example LCP), to report them relative to this soft navigation time instead.

The navigation start time can be obtained in a similar manner by mapping to the appropriate `soft-navigation` entry and using its `startTime`.

The `startTime` is the time of the initial interaction (for example, a button click) that initiated the soft navigation. This is somewhat different to "hard navigations", where the "start time" is when the new page is "committed", to the browser, and after some of the event handler code is run. Soft navigation start times also include that event handler code since we measure from the interaction start time.

### Measure Core Web Vitals per soft navigation

To measure Core Web Vitals, listen to `soft-navigation` entries, reset the metrics on receiving these. FCP can be emitted based on the `presentationTime` and LCP can be initialized to the `getLargestInteractionContentfulPaint()` entry. INP, CLS, should be initialized to 0 as they would be for a page load.

The LCP, INP, and CLS can then be measured and monitored as usual (with the exception of using `interaction-contentful-paint` for LCP providing the `interactionId` matches). The `interactionId` can be used to name the entries to a URL as [discussed previously](https://developer.chrome.com/docs/web-platform/soft-navigations#metrics-url).

Timings will still be returned relative to the original "hard" navigation start time. So to calculate LCP for a soft navigation for example, you will need to take the `interaction-contentful-paint` timing and subtract the appropriate soft navigation start time as [detailed previously](https://developer.chrome.com/docs/web-platform/soft-navigations#start-time) to get a timing relative to the soft navigation.

Some metrics have traditionally been measured throughout the life of the page: LCP, for example, can change until an interaction occurs. CLS and INP can be updated until the page is navigated away from, regardless of any interactions. Therefore the previous navigation's metrics should be finalized as each new soft navigation occurs. This means the initial "hard" navigation metrics may be finalized earlier than usual when measuring Core Web Vitals with soft navigations.

Similarly, when starting to measure the metrics for the new soft navigation of these long-lived metrics, metrics will need to be "reset" or "reinitialized" and treated as new metrics, with no memory of the values that were set for previous "pages". That is, the understanding of what the "largest" paint, interaction to next paint, or layout shift is reset to allow measuring again from scratch.

### How should content that remains the same between navigations be treated?

LCP for soft navigations (calculated from `interaction-contentful-paint`) will measure new paints only, and only paints associated with the interaction that caused the navigation. This can result in a different LCP from a cold load of that soft navigation to a soft load.

For example, take a page that includes a large banner image that is the LCP element, but the text beneath it changes with each soft navigation. The initial page load will flag the banner image as the LCP element and base the LCP timing on that. For subsequent soft navigations, the text beneath will be the largest element painted after the soft navigation and will be the new LCP element. However, if the page is loaded with a deep link into the soft navigation URL, the banner image will be a new paint and therefore will be eligible to be considered as the LCP element.

Similarly, an animation may be updating part of the page continually---unrelated to any soft navigation that happens. Any new paints due to that background animation wouldn't be considered for LCP for the new soft navigation. However they may be considered for LCP it the page was reloaded from this URL.

As these examples show, the LCP element for the soft navigation can be reported differently depending on how the page is loaded---in the same way as loading a page with an anchor link further down the page can result in a different LCP element for hard navigations.

> [!TIP]
> **Tip:** : When collecting RUM data you should include the `navigationType` to allow segmenting LCP (and also INP and CLS) differently for soft navigations since they may have difference characteristics.

### How to measure TTFB?

[Time to First Byte (TTFB)](https://web.dev/articles/ttfb) for a conventional page load represents the time that the first bytes of the original request are returned.

For a soft navigation this is a more tricky question. Should we measure the first request made for the new page? What if all the content already exists in the app and there are no additional requests? What if that request is made in advance with a prefetch? What if a request unrelated to the soft navigation from a user perspective (for example, it's an analytics request)?

A simpler method is to report TTFB of 0 for soft navigations---in a similar manner as we recommend for [back/forward cache](https://web.dev/articles/bfcache) restores. This is the method the [`web-vitals` library](https://developer.chrome.com/docs/web-platform/soft-navigations#web-vitals-library) uses for soft navigations and what we recommend for this metric at this time.

### Should you measure Core Web Vitals with both methodologies?

While these new APIs are limited to Chromium-based browsers only, sites may want to measure both by slicing by soft navigations, and by continuing to slice on hard navigations. This would allow comparison across browsers and for historical trends.

For LCP, then means considering just `largest-contentful-paint` entries for the current way, and both `largest-contentful-paint` and `interaction-contentful-paint` entries for the new way.

For CLS and INP, this means measuring these across the whole page lifecycle as is the case for the current way, and separately slicing the timeline by soft navigations to measure separate CLS and INP values for the new.

The metrics would then need to be beaconed and stored twice for analysis.

### Use the `web-vitals` library to measure Core Web Vitals for soft navigations

The easiest way to take account of all the nuances, is to use the [`web-vitals`](https://github.com/GoogleChrome/web-vitals) JavaScript library which has [support for soft navigations as of v6.0.0](https://github.com/GoogleChrome/web-vitals#report-metrics-for-soft-navigations). This can be measured in the following way (replacing `doTraditionalProcessing` and `doSoftNavProcessing` as appropriate):

    import {
      onTTFB,
      onFCP,
      onLCP,
      onCLS,
      onINP,
    } from 'https://unpkg.com/web-vitals@soft-navs/dist/web-vitals.js?module';

    function doTraditionalProcessing(callback) {
      ...
    }

    function doSoftNavProcessing(callback) {
      ...
    }

    onTTFB(doTraditionalProcessing);
    onFCP(doTraditionalProcessing);
    onLCP(doTraditionalProcessing);
    onCLS(doTraditionalProcessing);
    onINP(doTraditionalProcessing);

    onTTFB(doSoftNavProcessing, {reportSoftNavs: true});
    onFCP(doSoftNavProcessing, {reportSoftNavs: true});
    onLCP(doSoftNavProcessing, {reportSoftNavs: true});
    onCLS(doSoftNavProcessing, {reportSoftNavs: true});
    onINP(doSoftNavProcessing, {reportSoftNavs: true});

The `web-vitals` library also ensures you report the metrics are reported against the correct URL [as noted previously](https://developer.chrome.com/docs/web-platform/soft-navigations#metrics-url), as they include both the `navigationId` and a `navigationURL` in the entries provided to the callback.

The `web-vitals` library reports the following metrics for soft navigations:

| Metric | Details |
|---|---|
| TTFB | Reported as 0. |
| FCP | The time of the first contentful paint, relative to the soft navigation start time, from the interaction that triggered the soft navigation. Existing paints present from the previous navigation, or not associated with the interaction, are not considered. |
| LCP | The time of the largest contentful paint, relative to the soft navigation start time, from the interaction that triggered the soft navigation. Existing paints present from the previous navigation, are not associated with the interaction, are not considered. As usual, this can continue to update until the page (or the soft navigation) is navigated away from, as only then can the LCP be finalized. |
| INP | The INP between the navigation times. As usual, this can continue to update until the page (or the soft navigation) is navigated away from, as only then can the INP be finalized.. A 0 value is not reported if there are not interactions. Note the interaction causing a soft navigation is usually associated with the previous navigation (being navigated from), rather than the new navigation (being navigated to) since the first paint is needed to cause the soft navigation. This is similar to hard navigations where a link click may have an INP but which would be associated to the page the cliuck happened on. |
| CLS | The largest window of shifts between the navigation times. As usual, this can continue to update until the page (or the soft navigation) is navigated away from, as only then can the CLS be finalized. Note CLS associated with the navigation event may be excluded (if it happens within 500ms of the interaction), may be associated with the previous navigation (if the shift happens before the URL is updated), or may be associated with the new navigation (if the shift happens after soft navigation is finalized). |

## Will these changes become part of the Core Web Vitals measurements?

The ultimate aim is to provide a means to better measure performance as experiences by real users. So yes the aim is to include these in Core Web Vitals measurements as exposed by all tools after the API is launched.

### How will soft navigations be reported in CrUX?

We have launched experimental versions of the Core Web Vitals metrics in CrUX. These end the "hard nav" CLS and INP after the first soft navigation and also include additional LCP, CLS, and INP measures for each soft navigation. We plan to graduate these experimental metrics to stable metrics in May 2027. For more information see [Experimental soft navigation metrics now available in CrUX](https://web.dev/blog/crux-experimental-soft-navs) blog post.

### How to handle soft navigations in other browsers?

Soft navigations are difficult to programmatically detect in browsers that don't support the API---which is the very reason we have worked on this API.

It is possible to use the [Navigation API](https://developer.chrome.com/docs/web-platform/navigation-api) to monitor for URL changes which may allow slicing INP by URL (and similarly CLS, though it is Chromium-only at this time so wouldn't be measured regardless). However, this does not address the paint metrics (FCP and LCP) which can only be approximated.

At the same time, we believe the insights offered by this API are too valuable to ignore, both in measuring the timing of soft navigations and in attributing the metrics to the correct navigation. We recommend making use of this new API despite the lack of cross-browser support at this time. Much like when Core Web Vitals launched, issues identified, and peformance solutions will likely affect all browsers even if the visibility is limited to just Chromium-based browsers for now.

We will continue to work through the standardization process and engage with the other browser vendors to show the importance of this API.

## Feedback

We are actively seeking feedback on this API at the following places:

- Feedback on the API should be raised as [issues on GitHub](https://github.com/WICG/soft-navigations/issues).
- Bugs on the Chromium implementation should be [raised on Chrome's issue tracker](https://issues.chromium.org/issues/new?template_issue=434300302&component=1456231), if this is not one of the [known issues](https://issues.chromium.org/savedsearches/6917297) yet.
- General web vitals feedback can be shared at [web-vitals-feedback@googlegroups.com](mailto:web-vitals-feedback@googlegroups.com).

If in doubt don't worry too much, we'd rather hear the feedback in either place and will happily triage the issues in both places and redirect issues to the correct location.

## Changelog

As this API was developed, a number of changes have happened to it, more so than with stable APIs. You can see the [Soft Navigations Changelog](https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/speed/metrics_changelog/soft_navigations.md) for more details.

## Conclusion

The Soft Navigations feature is an exciting approach to how the Core Web Vitals initiative might evolve to measure a common pattern on the modern web that is missing from our metrics. We have gathered considerable feedback from broader web community and we are encouraged by the potential of this API.

## Acknowledgements

*Thumbnail image by [Jordan Madrid](https://unsplash.com/@jordanmadrid) on [Unsplash](https://unsplash.com/photos/NqOInJ-ttqM)*

*This work is a continuation of work first started by [Yoav Weiss](https://bsky.app/profile/yoav.ws) when he was at Google. We thank Yoav for his efforts on this API.*