спасибо

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

спасибо

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

  • 175
Loading comments...

More from Miguel Angel Durán García