One Step Ahead

A presentation at Smashing Conference in October 2025 in New York, NY, USA by Tim Kadlec

Slide 1

Slide 1

One Step Ahead Tim Kadlec @tkadlec.bsky.social linkedin.com/in/tkadlec/

Slide 2

Slide 2

100 150ms

Slide 3

Slide 3

100ms = 1%

Slide 4

Slide 4

100ms = 1% + 100ms = +.7%

Slide 5

Slide 5

100ms = 1% 100ms = 1% + + 100ms = +.7%

Slide 6

Slide 6

100ms = 1% 100ms = 1.1% 100ms = 1% + + + 100ms = +.7%

Slide 7

Slide 7

Slide 8

Slide 8

Slide 9

Slide 9

Expectations are not constant.

Slide 10

Slide 10

1 Jevons Paradox The more we have of a resource, the more we consume.

Slide 11

Slide 11

Slide 12

Slide 12

Slide 13

Slide 13

Slide 14

Slide 14

2 Hedonistic Treadmill r As resources become more powe ful, our expectations increase, diminishing our satisfaction.

Slide 15

Slide 15

Slide 16

Slide 16

Slide 17

Slide 17

r r Expectations are lea ned through expe ience.

Slide 18

Slide 18

Slide 19

Slide 19

Slide 20

Slide 20

Slide 21

Slide 21

Slide 22

Slide 22

r Oppo tunity.

Slide 23

Slide 23

Slide 24

Slide 24

Slide 25

Slide 25

Slide 26

Slide 26

r Core Web Vitals are a better sta ting point than a finish line.

Slide 27

Slide 27

Slide 28

Slide 28

r A retu n to the browser.

Slide 29

Slide 29

Slide 30

Slide 30

Slide 31

Slide 31

@view-transition { navigation: auto; }

Slide 32

Slide 32

Slide 33

Slide 33

Slide 34

Slide 34

<img

style=“ view-transition-name: u1-1748427501199-wemjtfslp” …

Slide 35

Slide 35

<img

style=“ view-transition-name: u1-1748427501199-wemjtfslp” …

Slide 36

Slide 36

<card-flip style=“ view-transition-name: u1-1748427416318-5btave6zs” … >

Slide 37

Slide 37

<card-flip style=“ view-transition-name: u1-1748427416318-5btave6zs” … >

Slide 38

Slide 38

Slide 39

Slide 39

Slide 40

Slide 40

Slide 41

Slide 41

Slide 42

Slide 42

Slide 43

Slide 43

<script type=”speculationrules”> { “prerender”: [{ “where”: { “href_matches”: “/*” }, “eagerness”: “moderate” }] } </script>

Slide 44

Slide 44

<script type=”speculationrules”> { “prerender”: [{ “where”: { “href_matches”: “/*” }, “eagerness”: “moderate” }] } </script>

What action to take

Slide 45

Slide 45

What action to take prefetch Fetch the HTML document. prerender Fetch, render and load the page in an invisible tab.

Slide 46

Slide 46

What action to take prefetch Less isk, less reward. Avoid prefetching pages that modify state. prerender r r More isk, more reward. Analytics & ads gotchas.

Slide 47

Slide 47

<script type=”speculationrules”> { “prerender”: [{ “where”: { “href_matches”: “/*” }, “eagerness”: “moderate” }] } </script>

What to apply it to

Slide 48

Slide 48

What to apply it to urls A list of specific URL’s where r A se ies of conditions to check against the document

Slide 49

Slide 49

{ } “prefetch”:[{ “urls”:[ “/page-two”, “/page-three” ] }]

Slide 50

Slide 50

{ “prefetch”: [ { “where”: { “and”: [ { “href_matches”: “/*” }, { “not”: {“href_matches”: “/logout”}} ] } } ] }

Slide 51

Slide 51

{ “prefetch”: [ { “where”: { “and”: [ { “href_matches”: “/*” }, { “not”: {“href_matches”: “/logout”}} ] } } ] }

Slide 52

Slide 52

{ “prefetch”: [ { “where”: { “and”: [ { “href_matches”: “/*” }, { “not”: {“href_matches”: “/logout”}} ] } } ] }

Slide 53

Slide 53

<script type=”speculationrules”> { “prerender”: [{ “where”: { “href_matches”: “/*” }, “eagerness”: “moderate” }] } </script>

When to apply it

Slide 54

Slide 54

immediate ASAP in idle time ASAP in idle time eager Experiment: Delay on hover (10ms) Planned: Eager Viewport Heuristics moderate Hovered over for 200ms, or pointerdown event Moderate viewport heuristics or pointerdown event Pointer or touch down Pointer or touch down conservative

Slide 55

Slide 55

immediate As soon as possible eager For now, same as immediate moderate Hovered over for 200ms, or pointerdown event conservative Pointer or touch down

Slide 56

Slide 56

r Using eage ness gives us a natural progression.

Slide 57

Slide 57

immediate ASAP in idle time ASAP in idle time eager Experiment: Delay on hover (10ms) Planned: Eager Viewport Heuristics moderate Hovered over for 200ms, or pointerdown event Moderate viewport heuristics or pointerdown event Pointer or touch down Pointer or touch down conservative

Slide 58

Slide 58

<script type=speculationrules> { “prefetch”: [ { “where”: { “selector_matches”: “[data-prefetch=”]” }, “eagerness”: “immediate” }, { “where”: { “and”: [ { “href_matches”: “/*” }, { “not”: { “selector_matches”: “[data-prefetch=false]” } } ] }, “eagerness”: “moderate” } ], “prerender”: [ { “where”: { “selector_matches”: “[data-prefetch=prerender]” }, “eagerness”: “immediate” }, { “where”: { “selector_matches”: “[data-prefetch=”]” }, “eagerness”: “moderate” } ] } </script>

https://csswizardry.com/2024/12/a-layered-approach-to-speculation-rules/

Slide 59

Slide 59

<script type=speculationrules> { “prefetch”: [ { “where”: { “selector_matches”: “[data-prefetch=”]” }, “eagerness”: “immediate” }, { “where”: { “and”: [ { “href_matches”: “/*” }, { “not”: { “selector_matches”: “[data-prefetch=false]” } } ] }, “eagerness”: “moderate” } ], “prerender”: [ { “where”: { “selector_matches”: “[data-prefetch=prerender]” }, “eagerness”: “immediate” }, { “where”: { “selector_matches”: “[data-prefetch=”]” }, “eagerness”: “moderate” } ] } </script>

Slide 60

Slide 60

<script type=speculationrules> { “prefetch”: [ { “where”: { “selector_matches”: “[data-prefetch=”]” }, “eagerness”: “immediate” }, { “where”: { “and”: [ { “href_matches”: “/*” }, { “not”: { “selector_matches”: “[data-prefetch=false]” } } ] }, “eagerness”: “moderate” } ], “prerender”: [ { “where”: { “selector_matches”: “[data-prefetch=prerender]” }, “eagerness”: “immediate” }, { “where”: { “selector_matches”: “[data-prefetch=”]” }, “eagerness”: “moderate” } ] } </script>

“prefetch”: [ { “where”: { “selector_matches”: “[data-prefetch=”]” }, “eagerness”: “immediate” }, <a href=”/link” data-prefetch>

Slide 61

Slide 61

<script type=speculationrules> { “prefetch”: [ { “where”: { “selector_matches”: “[data-prefetch=”]” }, “eagerness”: “immediate” }, { “where”: { “and”: [ { “href_matches”: “/*” }, { “not”: { “selector_matches”: “[data-prefetch=false]” } } ] }, “eagerness”: “moderate” } ], “prerender”: [ { “where”: { “selector_matches”: “[data-prefetch=prerender]” }, “eagerness”: “immediate” }, { “where”: { “selector_matches”: “[data-prefetch=”]” }, “eagerness”: “moderate” } ] } </script>

Slide 62

Slide 62

<script type=speculationrules> { “prefetch”: [ { “where”: { “selector_matches”: “[data-prefetch=”]” }, “eagerness”: “immediate” }, { “where”: { “and”: [ { “href_matches”: “/*” }, { “not”: { “selector_matches”: “[data-prefetch=false]” } } ] }, “eagerness”: “moderate” } ], “prerender”: [ { “where”: { “selector_matches”: “[data-prefetch=prerender]” }, “eagerness”: “immediate” }, { “where”: { “selector_matches”: “[data-prefetch=”]” }, “eagerness”: “moderate” } ] } </script>

{ “where”: { “and”: [ { “href_matches”: “/*” }, { “not”: { “selector_matches”: “[data-prefetch=false]” } } ] }, “eagerness”: “moderate” } <a href=”/less-important-link”> <a href=”https://google.com”>

Slide 63

Slide 63

<script type=speculationrules> { “prefetch”: [ { “where”: { “selector_matches”: “[data-prefetch=”]” }, “eagerness”: “immediate” }, { “where”: { “and”: [ { “href_matches”: “/*” }, { “not”: { “selector_matches”: “[data-prefetch=false]” } } ] }, “eagerness”: “moderate” } ], “prerender”: [ { “where”: { “selector_matches”: “[data-prefetch=prerender]” }, “eagerness”: “immediate” }, { “where”: { “selector_matches”: “[data-prefetch=”]” }, “eagerness”: “moderate” } ] } </script>

{ “where”: { “and”: [ { “href_matches”: “/*” }, { “not”: { “selector_matches”: “[data-prefetch=false]” } } ] }, “eagerness”: “moderate” } <a href=”/less-important-link”> <a href=”https://google.com”>

Slide 64

Slide 64

<script type=speculationrules> { “prefetch”: [ { “where”: { “selector_matches”: “[data-prefetch=”]” }, “eagerness”: “immediate” }, { “where”: { “and”: [ { “href_matches”: “/*” }, { “not”: { “selector_matches”: “[data-prefetch=false]” } } ] }, “eagerness”: “moderate” } ], “prerender”: [ { “where”: { “selector_matches”: “[data-prefetch=prerender]” }, “eagerness”: “immediate” }, { “where”: { “selector_matches”: “[data-prefetch=”]” }, “eagerness”: “moderate” } ] } </script>

Slide 65

Slide 65

<script type=speculationrules> { “prefetch”: [ { “where”: { “selector_matches”: “[data-prefetch=”]” }, “eagerness”: “immediate” }, { “where”: { “and”: [ { “href_matches”: “/*” }, { “not”: { “selector_matches”: “[data-prefetch=false]” } } ] }, “eagerness”: “moderate” } ], “prerender”: [ { “where”: { “selector_matches”: “[data-prefetch=prerender]” }, “eagerness”: “immediate” }, { “where”: { “selector_matches”: “[data-prefetch=”]” }, “eagerness”: “moderate” } ] } </script> <a href=”/prerender-me” data-prefetch=”prerender”> { “where”: { “selector_matches”: “[data-prefetch=prerender]” }, “eagerness”: “immediate” },

Slide 66

Slide 66

<script type=speculationrules> { “prefetch”: [ { “where”: { “selector_matches”: “[data-prefetch=”]” }, “eagerness”: “immediate” }, { “where”: { “and”: [ { “href_matches”: “/*” }, { “not”: { “selector_matches”: “[data-prefetch=false]” } } ] }, “eagerness”: “moderate” } ], “prerender”: [ { “where”: { “selector_matches”: “[data-prefetch=prerender]” }, “eagerness”: “immediate” }, { “where”: { “selector_matches”: “[data-prefetch=”]” }, “eagerness”: “moderate” } ] } </script>

Slide 67

Slide 67

<script type=speculationrules> { “prefetch”: [ { “where”: { “selector_matches”: “[data-prefetch=”]” }, “eagerness”: “immediate” }, { “where”: { “and”: [ { “href_matches”: “/*” }, { “not”: { “selector_matches”: “[data-prefetch=false]” } } ] }, “eagerness”: “moderate” } ], “prerender”: [ { “where”: { “selector_matches”: “[data-prefetch=prerender]” }, “eagerness”: “immediate” }, { “where”: { “selector_matches”: “[data-prefetch=”]” }, “eagerness”: “moderate” } ] } </script> <a href=”/link” data-prefetch> { “where”: { “selector_matches”: “[data-prefetch=”]” }, “eagerness”: “moderate” }

Slide 68

Slide 68

<script type=speculationrules> { “prefetch”: [ { “where”: { “selector_matches”: “[data-prefetch=”]” }, “eagerness”: “immediate” }, { “where”: { “and”: [ { “href_matches”: “/*” }, { “not”: { “selector_matches”: “[data-prefetch=false]” } } ] }, “eagerness”: “moderate” } ], “prerender”: [ { “where”: { “selector_matches”: “[data-prefetch=prerender]” }, “eagerness”: “immediate” }, { “where”: { “selector_matches”: “[data-prefetch=”]” }, “eagerness”: “moderate” } ] } </script>

Slide 69

Slide 69

<script type=speculationrules> { “prefetch”: [ { “where”: { “selector_matches”: “[data-prefetch=”]” }, “eagerness”: “immediate” }, { “where”: { “and”: [ { “href_matches”: “/*” }, { “not”: { “selector_matches”: “[data-prefetch=false]” } } ] }, “eagerness”: “moderate” } ], “prerender”: [ { “where”: { “selector_matches”: “[data-prefetch=prerender]” }, “eagerness”: “immediate” }, { “where”: { “selector_matches”: “[data-prefetch=”]” }, “eagerness”: “moderate” } ] } </script>

Slide 70

Slide 70

Clear-Site-Data: prefetchCache, prerenderCache

Slide 71

Slide 71

<script type=”speculationrules”> { “prefetch”: [ { “where”: { “and”: [ { “selector_matches”: “[data-prefetch=”]” }, { “not”: {“href_matches”: “/logout”}} ] }, “eagerness”: “immediate” } ], “prerender”: [ { “where”: { “selector_matches”: “[data-prefetch=prerender]” }, “eagerness”: “immediate” }, { “where”: { “and”: [ { “selector_matches”: “[data-prefetch=”]” }, { “not”: {“href_matches”: “/logout”}} ] }, “eagerness”: “moderate” } ] } </script>

Slide 72

Slide 72

<script type=”speculationrules”> { “prefetch”: [ { “where”: { “and”: [ { “selector_matches”: “[data-prefetch=”]” }, { “not”: {“href_matches”: “/logout”}} ] }, “eagerness”: “immediate” } ], “prerender”: [ { “where”: { “selector_matches”: “[data-prefetch=prerender]” }, “eagerness”: “immediate” }, { “where”: { “and”: [ { “selector_matches”: “[data-prefetch=”]” }, { “not”: {“href_matches”: “/logout”}} ] }, “eagerness”: “moderate” } ] } </script>

Slide 73

Slide 73

Slide 74

Slide 74

Slide 75

Slide 75

Slide 76

Slide 76

Slide 77

Slide 77

Slide 78

Slide 78

Slide 79

Slide 79

prefetch prerender immediate/eager 50 10 moderate/conservative 2 2

Slide 80

Slide 80

Slide 81

Slide 81

Slide 82

Slide 82

Slide 83

Slide 83

Slide 84

Slide 84

Slide 85

Slide 85

The fundamentals still matter.

Slide 86

Slide 86

Slide 87

Slide 87

Slide 88

Slide 88

Slide 89

Slide 89

Slide 90

Slide 90

@view-transition { navigation: auto; } <card-flip style=“ view-transition-name: u1-1748427416318-5btave6zs” … > <img style=“ view-transition-name: u1-1748427501199-wemjtfslp” … >

Slide 91

Slide 91

<script type=”speculationrules”> { “prefetch”: [ { “where”: { “and”: [ { “selector_matches”: “[data-prefetch=”]” }, { “not”: {“href_matches”: “/logout”}} ] }, “eagerness”: “immediate” } ], “prerender”: [ { “where”: { “selector_matches”: “[data-prefetch=prerender]” }, “eagerness”: “immediate” }, { “where”: { “and”: [ { “selector_matches”: “[data-prefetch=”]” }, { “not”: {“href_matches”: “/logout”}} ] }, “eagerness”: “moderate” } ] } </script>

Slide 92

Slide 92

: : : HTML 2 CSS 3 JSON 30

Slide 93

Slide 93

Slide 94

Slide 94

@keyframes spin-out { 0% { opacity: 1; transform: rotate(0deg) scale(1); } 100% { opacity: 0; transform: rotate(180deg) scale(0); } } @keyframes pop-in { 0% { opacity: 0; transform: scale(0.5) rotate(-180deg); } 100% { opacity: 1; transform: scale(1) rotate(0deg); } } /* Apply the animations to the view transition pseudo-elements */ ::view-transition-old(vitaly) { animation: 300ms ease-out forwards spin-out; } ::view-transition-new(vitaly) { animation: 500ms cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards pop-in; }

Slide 95

Slide 95

: CSS 10

Slide 96

Slide 96

Freakin’ awesome.

Slide 97

Slide 97

r Betting on the browser is our best chance at long-te m success.

Slide 98

Slide 98

r The best abstractions are the ones that are willing to fade away when they’re no longer necessa y.

Slide 99

Slide 99

r r r r Good pe fo mance requires a uthless obsession for the user expe ience.

Slide 100

Slide 100

Let’s get to work.

Slide 101

Slide 101

Thank you Tim Kadlec @tkadlec.bsky.social linkedin.com/in/tkadlec/