
спасибо
thank you

I'm Miguel Ángel Durán

@midudev

youtube.com/midudev

🇪🇸
🇷🇺
✈️
Enabler Frontend at


👨💻
@midudev
realtime poll 🗳


@midudev
What frontend library do you use?
Text


realtime poll results 🗳
@midudev


📷 🏠



@midudev

february 2019



@midudev





global score
@midudev
🏋️♂️
🏋️♂️

1.
2.
3.
4.
@midudev
Time to interactive:

february 2019
15 seconds

📹
@midudev

@midudev
Time to interactive:

february 2019
15 seconds

Is slow?
We asked ourselves...

@midudev


today

february 2019
@midudev

How do we have achieved this?

-2 seconds
-1 second
@midudev
Getting the most out of performance 🚀
while keeping bots happy 🤖

rendering strategies

@midudev
the problem
Understanding

@midudev
the problem
is...
do stuff on the client is expensive

and
@midudev
quick recap
rendering on the web

@midudev

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





33% market share worldwide
👍 Time To Interactive
First Contentful Paint
SEO Friendly
@midudev
Static Rendering



👍 TTI & FCP
Fast TTFB!
Static files
👎 Inflexible

👀 hydratation could be needed
export
@midudev
Client Side Rendering




👍 Flexible
Fast TTFB and FCP!
Just static files
👎 TTI way after FCP
No SEO friendly
@midudev

ReactDOM.render(
<App />,
document.getElementById('root')
)
Client Side Rendering
with
trying to get the best of two worlds...
SSR 🌍 + CSR 🌎
SSR with (re)hydration 💦
@midudev
SSR with (re)hydration 💦


hydration data
html from server



}
@midudev


that's hydration data
and it looks like this...


@midudev

// SERVER SIDE simplified
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 💦
// CLIENT SIDE
ReactDOM.hydrate(
<App initialData={window.__INITIAL_DATA__} />,
document.getElementById('root')
)
🏁 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

}


⏳

ReactDOM.hydrate
from server
client after hydration
@midudev
SSR with (re)hydration


frameworks
@midudev



SSR with (re)hydration 💦
getting the best...

👎 Slow TTFB (from SSR)
TTI way after FCP (from CSR)
or the worst of both?
👍 Flexible
Universal code
Fast TTFB and FCP
SEO Friendly
@midudev
SSR
with (re)hydration
DEMO

@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
Mozilla/5.0 (compatible; YandexBot/3.0; +http://yandex.com/bots)


middleware
@midudev
Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:69.0) Gecko/20100101 Firefox/69.0


middleware
@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
Dynamic Tower for Moscow
cancelled project - 2008
@midudev
Dynamic
Rendering
by route
♻️ Fast TTFB
🧹 Hydration data is gone
1️⃣ Only changes on the server side

🧪 Useful technique for perf experiments
🆓 Free resources from your server
⏳ SEO Friendly
🤔 Client Side Rendering for user
🚩 Bad first paint

@midudev
Does like it?

Dynamic Rendering is not cloaking
Make sure that the pages return the full content to the robot. If they use JavaScript code, the robot will not be able to index the content generated by the script.
@midudev

@midudev

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

@midudev
Is Dynamic Rendering worth it?
🤔🚩
so...
@midudev
Dynamic
Rendering
at component level

@midudev


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

⚛️
🤖 🌍
💁♂️ 🌍




👀
👀
@midudev
How to use & implementation

@midudev

// ⚛️ Using <DynamicRendering /> component
// userAgent must be retrieved universally
// on server and client
<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 }) {
// check if the userAgent is a bot
const isBot = checkUserAgentIsBot({userAgent})
// if isBot, we return in server and client the content
if (isBot) return children
// now, we're sure the user is NOT a bot
// so check if we're on the browser side
if (typeof window !== 'undefined') {
return <LazyContent>{children}</LazyContent>
} else {
// so, we're on the server side or the component is disabled
return <div style={{border: '1px solid red'}} />
}
}

A real use case scenario
@midudev
in production


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


without Dynamic Rendering

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

@midudev
Only 1️⃣ requirement:
It's open source!
you need to use React ⚛️
# npm
npm install @midudev/react-dynamic-rendering
# yarn
yarn add @midudev/react-dynamic-rendering
Fully compatible with:

@midudev


📦

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

✅ Improve TTI
😴 Lazy Load for the user
🏋️♀️ Perfect for stuff below the fold
🆓 Free resources from your server
⏳ Help GoogleBot to index your content faster
☝️ More about this, now 👉

🚩 Keep Hydration data
👩🔬 Need universal UA calculation
🤖 Bot still gets full cost
@midudev
Why do we need this?
The content you want to include in the search should be available in the HTML code immediately after requesting the page, without using JavaScript code.

client side rendering future
@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
https://youtu.be/LXF8bM4g-J4

want/need to be
crawled fast and indexed faster
use server-side rendering.
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

@midudev

@midudev
Static
Rendering
DEMO
@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
Only 1️⃣ requirement:
It's open source!
you need to use React ⚛️
# npm
npm install @midudev/react-static-content
# yarn
yarn add @midudev/react-static-content
Fully compatible with:

@midudev


dependencies-free!
@midudev
Can we do it better?
Can we do it progressive?
@midudev


@midudev


Static
Rendering
Dynamic
Rendering
at component level
➕
@midudev



👀

👀

👀
👈 Static HTML
👈 Static HTML
👈 Static HTML
@midudev
Progressive
Hydration

@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 🤯
@midudev/react-progressive-hydration
Progressive Hydration
Breaks connectivity between React trees ⚛️ 🌳


@midudev
Progressive
Hydration
DEMO

@midudev


👀 Only re-hydrate what's visible
🤳 Thus could greatly improve TTI
🔛 Activate interactivity on demand
📸 Kind of 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
Only 1️⃣ requirement:
It's open source!
you need to use React ⚛️
# npm
npm install @midudev/react-progressive-hydration
# yarn
yarn add @midudev/react-progressive-hydration
Fully compatible with:

@midudev


@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
First class support
on React?


@midudev

Progressive Hydration
Selective Hydration


Yuzhi Zheng at React Conf 2019
@midudev


Actual SSR with hydration
@midudev
hydrate on the client 💦
while streaming from the server 📡
Future SSR with hydration
📹
@midudev
Selective Hydration
@midudev
// npm install react@experimental react-dom@experimental
import ReactDOM from 'react-dom'
// If you previously had:
//
// ReactDOM.render(<App />, document.getElementById('root'));
//
// You can opt into Concurrent Mode by writing:
ReactDOM.createRoot(
document.getElementById('root')
).render(<App />)
Enable Concurrent Mode

so...
@midudev

How have we achieved this?


Static
Rendering
Dynamic
Rendering

@midudev

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

@midudev

rendering strategies

@midudev
🔗 https://midu.dev/holyjs-links

спасибо
thanks!

all links &
resources


@midudev




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

available on:

[HolyJS] - React Rendering Strategies
By Miguel Angel Durán García
[HolyJS] - React Rendering Strategies
Version: HolyJS 2019
- 725