спасибо
thank you
🇪🇸
🇷🇺
✈️
@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
@midudev
@midudev
@midudev
@midudev
👎 Slow Time to First Byte
Maintain different codebases
33% market share worldwide
👍 Time To Interactive
First Contentful Paint
SEO Friendly
@midudev
👍 TTI & FCP
Fast TTFB!
Static files
👎 Inflexible
👀 hydratation could be needed
export
@midudev
👍 Flexible
Fast TTFB and FCP!
Just static files
👎 TTI way after FCP
No SEO friendly
@midudev
ReactDOM.render(
<App />,
document.getElementById('root')
)
@midudev
hydration data
html from server
}
@midudev
@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>
`)
// CLIENT SIDE
ReactDOM.hydrate(
<App initialData={window.__INITIAL_DATA__} />,
document.getElementById('root')
)
🏁 Attach event listeners to the existing markup
🔎 Check mismatches between server-side and client-side
🌳 Recreate tree on the virtual dom
🚀 Set up life cycles order and fire them
⚛️ constructor, componentDidMount, useEffect, ...
ReactDOM.hydrate
app.$mount('#root')
@midudev
}
⏳
ReactDOM.hydrate
from server
client after hydration
@midudev
@midudev
👎 Slow TTFB (from SSR)
TTI way after FCP (from CSR)
👍 Flexible
Universal code
Fast TTFB and FCP
SEO Friendly
@midudev
@midudev
@midudev
@midudev
@midudev
@midudev
@midudev
@midudev
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
@midudev
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
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
@midudev
🤔🚩
@midudev
at component level
@midudev
@midudev
at component level
@midudev
⚛️
🤖 🌍
💁♂️ 🌍
👀
👀
@midudev
@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'}} />
}
}
@midudev
📏 ~700px visible
>20000px rendered 👉
😱
👀 only 1 card
34 cards
@midudev
@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
@midudev
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
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.
@midudev
@midudev
links detected
at a later time
💸 expensive!
https://youtu.be/LXF8bM4g-J4
WRS
@midudev
https://youtu.be/LXF8bM4g-J4
@midudev
@midudev
@midudev
at component level
@midudev
🏁 Attach event listeners to the existing markup
🔎 Check mismatches between server-side and client-side
🌳 Recreate tree on the virtual dom
🚀 Set up life cycles order and fire them
constructor, componentDidMount, useEffect, ...
@midudev
@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
@midudev
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
@midudev
@midudev
at component level
➕
@midudev
👀
👀
👀
@midudev
@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
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
@midudev
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
@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
@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
@midudev
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
@midudev
@midudev
@midudev
@midudev
@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 />)
@midudev
How have we achieved this?
@midudev
is could be slow.
Now you have
strategies to fix it.
@midudev
rendering strategies
@midudev
🔗 https://midu.dev/holyjs-links
спасибо
thanks!
@midudev
@midudev/react-dynamic-rendering
@midudev/react-static-content
@midudev/react-progressive-hydration