


thank you very much
dziękuję bardzo

I'm Miguel Ángel Durán 👋🇪🇸

@midudev

youtube.com/midudev

#performance⚡️ #react ⚛️
Frontend Architect at

👨💻

@midudev
What frontend library
do you use?
Text




@midudev


📷 🏠



@midudev

february 2019



Speed Index
7s
Performance Score
Time to Interactive
15s
🏋️ x 4
🏋️ x 5
@midudev

@midudev
TTI
15 seconds

📹
@midudev



❤️

@midudev

Is slow?
We asked ourselves...

@midudev

february 2019

Speed Index
7s
Performance Score
Time to Interactive
15s
@midudev

today

Speed Index
3.9s
Performance Score
Time to Interactive
7s

@midudev

Our problem is not React anymore

@midudev



@midudev

How do we have achieved this?

-2s
-1.5s



@midudev
Getting the most out of performance 🚀
while keeping bots happy 🤖

rendering strategies

@midudev
the problem
Understanding

@midudev
the problem
is...
render is expensive

and
specially on the client 📱
@midudev
Static Rendering



Time to Interactive
Other performance metrics
Static files

👀 hydration could be needed
export
Inflexible
✅
❌

@midudev

Server Side Rendering (SSR)
Slow Time to First Byte
Maintain different codebases




✅
Time To Interactive
First Contentful Paint
SEO Friendly
❌

@midudev
Client Side Rendering




Flexible
Fast TTFB and FCP!
Just static files
TTI way after FCP
SEO concerns
✅
❌
@midudev
trying to get the best of two worlds...
SSR with (re)hydration 💦
SSR 🌍 + CSR 🌎

@midudev
SSR with (re)hydration 💦


hydration data
html from server


}

1st step: server side
@midudev


const initialData = await getInitialData()
const html = ReactDOM.renderToString(
<App initialData={initialData} />
)
res.send(`
<div id='root'>${html}</div>
<script>
window.__INITIAL_DATA__ = ${JSON.stringify(initialData)}
</script>`
)
with
SSR with (re)hydration 💦
ReactDOM.hydrate(
<App initialData={window.__INITIAL_DATA__} />,
document.getElementById('root')
)
// SERVER SIDE simplified
// CLIENT SIDE simplified
🏁 Attach event listeners to the existing markup
💦 Hydration
🔎 Check mismatches between server-side and client-side
🌳 Recreate tree on the virtual dom
🚀 Set up life cycles order and fire them
a.k.a bootstraping or initialization
⚛️ constructor, componentDidMount, useEffect, ...

ReactDOM.hydrate

app.$mount('#root')
@midudev
SSR with (re)hydration 💦

}


⏳

ReactDOM.hydrate
from server
client after hydration
2nd step: client side
@midudev


frameworks



SSR with (re)hydration 💦
@midudev
Universal code
SSR with (re)hydration 💦
the best of the two worlds?

Flexible
SEO Friendly
Faster Speed Index
@midudev
Slow Time To First Byte from SSR
Bad Time to Interactive from CSR
SSR with (re)hydration 💦
... or the worst?

@midudev
SSR
with (re)hydration
DEMO

@midudev


6x

1.8s

43KB
SSR
with (re)hydration
🗜️
@midudev
a solution

Finding
@midudev
but... how?
🤖 SEO
I still want to...
⚛️ Flexibility
🤳 User Experience (UX)
👩💻 Developer Experience (DX)
@midudev

rendering strategies

@midudev
Dynamic Rendering
starting with...

@midudev
Dynamic Rendering
what is...

🤖
👫
@midudev
Strategies with
Dynamic Rendering

by Route
by Component


@midudev
Dynamic
Rendering
by route

@midudev
// 🚇 middleware to activate Dynamic Rendering on Express
const BOTS_USER_AGENTS = [
'googlebot',
'mediapartners-google',
'yandexbot'
]
const INDEX_HTML_PATH = path.join(__dirname, 'public', 'index.html')
const HTML_TEMPLATE = fs.readFileSync(INDEX_HTML_PATH, 'utf8')
module.exports = function middleware(req, res, next) {
const rawUserAgent = req.get('user-agent')
const userAgent = rawUserAgent.toLowerCase()
// check if the request comes from a bot 🤖
if (BOTS_USER_AGENTS.find(ua => userAgent.includes(ua))) {
// if not an user, just use the normal behaviour (SSR?)
return next()
}
// return index.html directly if is an user 👩💻
return res.send(HTML_TEMPLATE)
}

// 🚆 Using the middleware in Express
const dynamicRendering = require('./dynamic-rendering')
// Use dynamic rendering for a specific path for testing
app.get(
'/es/:op/:type/barcelona-capital/*',
dynamicRenderingExperiment
)
// Isomorphic routes handler
app.get('*', serverSideRenderingMiddleware)
next()
response html


👩💻
🤖

Dynamic
Rendering
at route level
DEMO

@midudev
Dynamic
Rendering
by route
♻️ Fast TTFB
🧹 Hydration data is gone
1️⃣ Only changes on the server side
🤖 SEO Friendly

🧪 Useful technique for perf experiments
🆓 Free resources from your server
🤔 Just a Client Side Rendering for the user

@midudev
Dynamic
Rendering
at component level

@midudev


ServerSide Rendering
with (re)hydration
⚛️
@midudev
Dynamic Rendering
at component level
with
...
@midudev

⚛️🌲
🤖 🌍
💁♂️ 🌍




👀
👀
viewport
@midudev
How to use & implementation

@midudev

<DynamicRendering userAgent={universalUserAgent}>
<a href='https://very-interesting-url.com'>
<VeryComplexToComputeComponent />
<img src='https://huge-image.com/panda.jpg' />
</a>
</DynamicRendering>
@schibstedspain/react-perf-dynamic-rendering
http://bit.ly/sui-dynamic-rendering
// ⚛️ <DynamicRendering /> ~"kinda" implementation
export default function DynamicRendering({ children, userAgent }) {
const isBot = checkUserAgentIsBot({userAgent})
if (isBot) return children
if (typeof window !== 'undefined') { // isBrowser
return <LazyContent>{children}</LazyContent>
} else { // isServer
return <div style={{border: '1px solid red'}} />
}
}

A real use case scenario
in production
@midudev


📏 ~700px visible
>20000px rendered 👉
😱
👀 only 1 card
34 cards


without Dynamic Rendering

~525ms
with Dynamic Rendering
~107ms
-80% reduction ✂️


1x
@midudev
Dynamic
Rendering
at component level
DEMO
@midudev

6x

~350ms
23KB
🗜️
DynamicRendering
at component level


-1.5s
-20KB
Dynamic
Rendering
at component level

✅ Improve TTI
😴 Lazy Load for the user
🏋️♀️ Perfect for stuff below the fold
⏳ Help GoogleBot to index your content faster

🚩 Keep Hydration data
👩🔬 Need universal UA calculation
🤖 Bot still gets full cost SSR with hydration
@midudev

Does like it?
I hope it does.
It's their idea! 💡

@midudev
The new bots of Google are superb!
🧩 JS Modules
📐 Intersection Observer
🧱 Custom Elements
🌚 Shadow DOM
🏫 Classes
🏷 Tagged Template Literals


Why do we need this?

@midudev


links detected
at a later time
💸 expensive!
https://youtu.be/LXF8bM4g-J4

130 trillion web pages to crawl
rendering all those pages, takes time

WRS

Google Bot 101

@midudev
want/need to be
crawled fast and indexed faster
use server-side rendering. for now.
tl;dl 👂
@midudev
also SSR is usually faster than CSR
⚡
@midudev


@midudev
Static
Rendering
at component level

@midudev
🏁 Attach event listeners to the existing markup
💦 Hydration
🔎 Check mismatches between server-side and client-side
🌳 Recreate tree on the virtual dom
🚀 Set up life cycles order and fire them
a.k.a bootstraping or initialization
constructor, componentDidMount, useEffect, ...
avoid the cost of this 👆
@midudev
How to use & implementation

@midudev
// ⚛️ <StaticContent /> usage
import StaticContent from './StaticContent'
function Footer () {
return (
<StaticContent>
<HugeListOfLinks data={listOfLinks} />
</StaticContent>
)
}

// ⚛️ <StaticContent /> ~implementation
import React from 'react'
export default function StaticContent({children}) {
// we're in the server, just render the content
if (typeof window === 'undefined') {
return <div>{children}</div>
}
// avoid re-render on the client
return (
<div
dangerouslySetInnerHTML={{__html: ''}}
suppressHydrationWarning
/>
)
}





⚛️ core contributor

dangerouslySetInnerHTML
about
@midudev


suppressHydrationWarning
about

@midudev
Static
Rendering
DEMO
@midudev

6x

~235ms
44KB
🗜️
Static Content
-1.6s
+1KB


@midudev
Static
Rendering
at component level

⚡ Avoid re-hydrate for static components
🤳 Thus could greatly improve TTI
📸 For expensive rendering lists or static content (SEO Footers)
🤖 GoogleBot is definitely going to detect it

⚠️ Lose interactivity!!!!!
🏋️♀️ Hydration data still there
🥪 Element wrapper (ex. <div>)
@midudev
Can we do it better?
Can we do it progressive?

@midudev


@midudev


Static
Rendering
Dynamic
Rendering
at component level
➕
@midudev
Progressive
Hydration

@midudev



👀

👀

👀
👈 Static HTML
👈 Static HTML
👈 Static HTML
@midudev
@schibstedspain/react-perf-dynamic-rendering
http://bit.ly/sui-dynamic-rendering
// ⚛️ How to use <ProgressiveRendering />
function ProgressiveHydrationUsage({articles}) {
return (
<Grid>
<h1>Articles</h1>
{articles.map(article => (
<ProgressiveHydration key={article.id}>
<Card {...article} />
</ProgressiveHydration>
))}
</Grid>
)
}

@schibstedspain/react-perf-dynamic-rendering
http://bit.ly/sui-dynamic-rendering
// ⚛️ <ProgressiveRendering /> ~"kinda" implementation
function ProgressiveHydration({children}) {
const ref = useRef(null)
const isNearScreen = useNearScreen({ref})
useEffect(() => {
// CLIENT: If the element is near screen, then hydrate it
if (isNearScreen) {
ReactDOM.hydrate(children, ref.current)
}
}, [children, isNearScreen])
// SERVER: Just render the content as usual
if (typeof window === 'undefined')
return <div ref={ref}>{children}</div>
// CLIENT: Avoid hydration until we say so
return (
<div ref={ref}
suppressHydrationWarning
dangerouslySetInnerHTML={{__html: ''}}
/>
)
}

vue-lazy-hydration by maoberlehner
Lazy hydration of server-side rendered Vue.js components
about

lazy-hydration by znck
Lazy Hydration for Vue SSR
Simple API. Plug and play.
Configurable interaction to fire hydration.
@midudev
<template>
<div class="ArticlePage">
<LazyHydrate when-idle>
<ImageSlider/>
</LazyHydrate>
<LazyHydrate ssr-only>
<ArticleContent :content="article.content"/>
</LazyHydrate>
<LazyHydrate when-visible>
<AdSlider/>
</LazyHydrate>
<!-- `on-interaction` listens for a `focus` by default ... -->
<LazyHydrate on-interaction>
<CommentForm :article-id="article.id"/>
</LazyHydrate>
<!-- ... but you can listen for any event you want ... -->
<LazyHydrate on-interaction="click">
<CommentForm :article-id="article.id"/>
</LazyHydrate>
<!-- ... or even multiple events. -->
<LazyHydrate :on-interaction="['click', 'touchstart']">
<CommentForm :article-id="article.id"/>
</LazyHydrate>
</div>
</template>
vue-lazy-hydration

about




@midudev
and
react-prerendered-component by theKashey
Partial Hydration and Component Level Caching
Powerful 💪 but complicated 🤹♂️
react-progressive-hydration by
Progressive Hydration
Experimental 🧪 but interesting 🤯
react-lazy-hydration by hadeeb
Not hydrating but rendering later 🎨. Based on vue lazy-hydration.


@midudev
Progressive
Hydration
DEMO

@midudev

6x

~235ms
44KB
🗜️
Progressive Hydration
-1.6s
+1KB
@midudev



~30ms
on demand


👀 Only hydrate what's visible
🤳 Thus could greatly improve TTI
🔛 Activate interactivity on demand
📸 Rich lazy loading experience
🤖 GoogleBot will get the rendered static html (not hydrated)
🏋️♀️ Hydration data still there
🥪 Element wrapper (ex. <div>)
⚠️ No Context passed down!!!!
Progressive
Hydration
@midudev

so, what's next?
@midudev
SSR in React could be much better
from Andrew Clarke presentation: https://youtu.be/z-6JC0_cOns?t=950
Can't start rendering anything until everything is loaded
Encapsulation is difficult (no state or lifecycles)
Need to hoist all async data
@midudev

Progressive Hydration
Selective Hydration


Yuzhi Zheng at React Conf 2019
@midudev


Actual SSR with hydration

@midudev
hydrate while streaming from the server 📡
Future SSR with hydration
📹
@midudev
ReactDOMServer.renderToNodeStream(
<App initialData={initialData} />
).pipe(res)
Selective Hydration
@midudev

so...
@midudev

How have we achieved this?


Static
Rendering
Dynamic
Rendering



@midudev

could be slow.
Now you have
strategies to fix it.

@midudev



Dynamic Rendering
@midudev/react-dynamic-rendering
Static Content
@midudev/react-static-content
Progressive Hydration
@midudev/react-progressive-hydration

available on:

by component
@midudev
Requirements:
isomorphic React ⚛️
and your custom solution



Compatibility:
Bundle size:



@midudev

rendering strategies

@midudev
all links &
resources


@midudev

thank you very much
dziękuję bardzo


Frontend Con - React Rendering Strategies
By Miguel Angel Durán García
Frontend Con - React Rendering Strategies
Version: Frontend Con
- 574