• Stars
    star
    164
  • Rank 230,032 (Top 5 %)
  • Language
    TypeScript
  • Created over 6 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

🤔Partial hydration and caching in a pre-suspense era

React Prerendered Component


Partial Hydration and Component-Level Caching

Idea

In short: dont try to run js code, and produce a react tree matching pre-rendered one, but use pre-rendered html until js code will be ready to replace it. Make it live.

What else could be done on HTML level? Caching, templatization, and other good things to 🚀, just in a 3kb*.

Prerendered component

Render something on server, and use it as HTML on the client

  • Server side render data
    • call thisIsServer somewhere, to setup enviroment.
    • React-prerendered-component will leave trails, wrapping each block with div with known id.
  • Hydrate the client side
    • React-prerendered-component will search for known ids, and read rendered HTML back from a page.
  • You site is ready!
    • React-prerendered-components are ready. They are rendering a pre-existing HTML you send from a server.
  • Once any component ready to be replaced - hydrate
    • But not before. That's the point - partial hydration, step by step

Bonus - you can store and restore component state.

More details - https://twitter.com/theKashey/status/1021739948536295424

Usage

  1. Restore data from HTML
<PrerenderedComponent
  // restore - access DIV and get "counter" from HTML
  restore={(el) => this.setState({counter: +el.querySelector('i').innerHTML})}
  // once we read anything - go live!
  live={!!this.state.counter}
>
  <p>Am I alive?</p>
  <i>{this.props.counter}</i>
</PrerenderedComponent>

Is components HTML was not generated during SSR, and it would be not present in the page code - component will go live automatically, unless strict prop is set.

  1. To do a partial hydrating

You may keep some pieces of your code as "raw HTML" until needed. This needed includes:

  • code splitting. You may decrease time to _First Meaningful Paint code splitting JS code needed by some blocks, keeping those blocks visible. Later, after js-chunk loaded, it will rehydrate existing HTML.
  • deferring content below the fold. The same code splitting, where loading if component might be triggered using interception observer.

If your code splitting library (not React.lazy) supports "preload" - you may use it to control code splitting

const AsyncLoadedComponent = loadable(() => import('./deferredComponent'));
const AsyncLoadedComponent = imported(() => import('./deferredComponent'));

<PrerenderedComponent
  live={AsyncLoadedComponent.preload()} // once Promise resolved - component will go live  
>
  <AsyncLoadedComponent />
</PrerenderedComponent>
  1. Restore state from JSON stored among.
<PrerenderedComponent
  // restore - access DIV and get "counter" from HTML
  restore={(_,state) => this.setState(state)}
  store={ this.state }
  // once we read anything - go live!
  live={!!this.state.counter}  
>
  <p>Am I alive?</p>
  <i>{this.props.counter}</i>
</PrerenderedComponent>

This is not SSR friendly unless....

Wrap your application with PrerenderedControler. This would provide context for all nested components, and "scope" counters used to represent nodes.

This is more Server Side requirement.

import {PrerenderedControler} from 'react-prerendered-component';

ReactDOM.renderToString(<PrerenderedControler><App /></PrerenderedControler>);

!!!!

Without PrerenderedControler SSR will always produce an unique HTML, you will be not able to match on Client Side.

!!!!

Client side-only components

It could be a case - some components should live only client-side, and completely skipped during SSR.

import {render} from 'react-dom';
import { ClientSideComponent } from "react-prerendered-component";

const controller = cacheControler(cache);

const result = render(
  <ClientSideComponent>
     Will be rendered only on client
  </ClientSideComponent>
);

const result = hydrate(
  <PrerenderedControler hydrated>
      <ClientSideComponent>
         Will be rendered only on client __AFTER__ hydrate
      </ClientSideComponent>
  </PrerenderedControler>
);  
  • There is the same component for ServerSideComponents
  • There are hoc version for both cases
import {clientSideComponent} from 'react-prerendered-component';

export default clientSideComponent(MyComponent);

Safe SSR-friendly code splitting

Partial rehydration could benefit not only SSR-enhanced applications, but provide a better experience for simple code splitting.

In the case of SSR it's quite important, and quite hard, to load all the used chunks before triggering hydrate method, or some unloaded parts would be replaced by "Loaders".

Preloaded could help here, if your code-splitting library support preloading, even if it does not support SSR.

import imported from 'react-imported-component';
import {PrerenderedComponent} from "react-prerendered-component";

const AsyncComponent = imported(() => import('./myComponent.js'));

<PrerenderedComponent
  // component will "go live" when chunk loading would be done
  live={AsyncComponent.preload()}
>
  // until component is not "live" prerendered HTML code would be used
  // that's why you need to `preload`
  <AsyncComponent/>
</PrerenderedComponent>

Yet again - it works with any library which could preload, which is literally any library except React.lazy.

Caching

Prerendered component could also work as a component-level cache. Component caching is completely safe, compatible with any React version, but - absolutely synchronous, thus no Memcache or Redis are possible.

import {renderToString, renderToNodeStream} from 'react-dom/server';
import {
  PrerenderedControler, 
  cacheControler, 
  CachedLocation, 
  cacheRenderedToString, 
  createCacheStream
} from "react-prerendered-component";

const controller = cacheControler(cache);

const result = renderToString(
  <PrerenderedControler control={control}>
     <CachedLocation cacheKey="the-key">
        any content
     </CachedLocation>
  </PrerenderedControler>
)

// DO NOT USE result! It contains some system information  
result === <x-cachedRANDOM_SEED-store-1>any content</x-cachedRANDOM_SEED-store-1>

// actual caching performed in another place
const theRealResult = cacheRenderedToString(result);

theRealResult  === "any content";


// Better use streams
renderToNodeStream(
  <PrerenderedControler control={control}>
     <CachedLocation cacheKey="the-key">
        any content
     </CachedLocation>
  </PrerenderedControler>
)
.pipe(createCacheStream(control)) // magic here
.pipe(res)

Stream API is completely stream and would not delay Time-To-First-Byte

  • PrerenderedControler - top level controller for a cache. Requires controler to be set
  • CachedLocation - location to be cached.
    • cacheKey - string - they key

    • ttl - number - time to live

    • refresh - boolean - flag to ignore cache

    • clientCache - boolean - flag to enable cache on clientSide (disabled by default)

    • noChange - boolean - disables cache at all

    • variables - object - varibles to use in templatization

    • as=span - string, used only for client-side cache to define a wrapper tag

    • className - string, used only for client-side cache

    • rehydrate - boolean, used only for client-side cache, false values would keep content as a dead html.

  • Placeholder - a template value
    • name - a variable name
  • WithPlaceholder - renderprop version of Placeholder
  • NotCacheable - mark location as non-cacheable, preventing memoization
  • cacheControler(cache) - a cache controller factor, requires object with cache interface to work.
    • cache interface is { get(key): string, set(key, ttl):void }
    • cache implimentation is NOT provided by this library.

Placeholder and Templatization

To optimize rendering performance and reduce memory usage you might use cache templates:

import {CachedLocation, Placeholder, WidthPlaceholder} from 'react-prerendered-component';

const Component = () => (
  <CachedLocation key="myKey" variabes={{name: "GitHub", secret: 42 }}>
    the <Placeholder name="name"/>`s secret is <Placeholder name="secret"/>
    // it's easy to use placeholders in a plain HTML 
    
    <WithPlaceholder>
    { placeholder => (
       <img src="./img.jpg" alt={placeholder("name") + placeholder("secret")}/>
       // but to use it in "string" attribures you have to use render props
    )}
    </WithPlaceholder>
  </CachedLocation>
)

NotCacheable

Sometimes you might got something, which is not cacheable. Sometimes cos you better not cache like - like personal information. Sometimes cos it reads data from variable sources and could not be "just cached". It is always hard to manage it. So - just dont cache. It's a one line fix.

import {NotCacheable, notCacheable} from 'react-prerendered-component';

const SafeCache = () => (
  <NotCacheable>
    <YourComponent />
  </NotCacheable>
);

const SafeComponent = notCacheable(YourComponent);

Sharing cache between multiple process

Any network based caches are not supported, the best cache you can use - LRU, is bound to single process, while you probably want multi-threaded(workers) rendering, but dont want to maintain per-instance cache.

You may use nodejs shared-memory libraries (not supported by nodejs itself), like:

Cache speed

Results from rendering a single page 1000 times. All tests executed twice to mitigate possible v8 optimizations.

dry      1013 - dry render to kick off HOT
base     868  - the __real__ rendering speed, about 1.1ms per page
cache    805  - with `cacheRenderedToString` used on uncachable appp
cache    801  - second run (probably this is the "real" speed)
partial  889  - with `cacheRenderedToString` used lightly cached app (the cost of caching)
partial  876  - second run
half     169  - page content cached
half     153  - second run
full     22   - full page caching
full     19   - second run
  • full page cache is 42x faster. 0.02ms per page render
  • half page render is 5x faster.
  • partial page render is 1.1x slower.

Prerendered support

It is safe to have prerendered component inside a cached location.

Additional API

  1. ServerSideComponent - component to be rendered only on server. Basically this is PrerenderedComponent with live=false
  2. ClientSideComponent - component to be rendered only on client. Some things are not subject for SSR. ClientSideComponent would not be initially rendered with hydrated prop enabled
  3. thisIsServer(flag) - override server/client flag
  4. isThisServer() - get current environment.

Automatically goes live

Prerendered component is work only once. Once it mounted for a first time.

Next time another UID will be generated, and it will not find component to match. If prerendered-component could not find corresponding component - it goes live automatically.

Testing

Idea about PrerenderedComponent is to render something, and rehydrate it back. You should be able to render the same, using rehydrated data.

  • render
  • restore
  • render
  • compare. If result is equal - you did it right.

While area is not "live" - it's dead

Until component go live - it's dead HTML code. You may be make it more alive by transforming HTML to React, using html-to-react, and go live in a few steps.

Size 🤯

Is this package 25kb? What are you thinking about?

  • no, this package is just 3kb or less - tree shaking is great (but not in a dev mode) It is bigger only on server.

See also

react-progressive-hydration - Google IO19 demo is quite similar to react-prerendered-component, but has a few differences.

  • does not use stable uids, utilizing __html:'' + sCU hack to prevent HTML update
  • uses React.hydrate to make component live, breaking connectivity between React Trees
  • while it has some benefits (no real HTML update), it might not be production ready right now

Licence

MIT

More Repositories

1

react-focus-lock

It is a trap! A lock for a Focus. 🔓
JavaScript
1,092
star
2

react-imported-component

✂️📦Bundler-independent solution for SSR-friendly code-splitting
TypeScript
651
star
3

react-remove-scroll

Removes and disables 📜in a "React" way
TypeScript
611
star
4

rewiremock

The right way to mock dependencies in Node.js or webpack environment.
JavaScript
442
star
5

memoize-state

The magic memoization for the State management. ✨🧠
JavaScript
324
star
6

react-focus-on

🎯 Solution for WAI ARIA compatible modal dialogs or full-screen tasks, you were looking for
TypeScript
271
star
7

use-callback-ref

🤙The same useRef, but it will callback
TypeScript
265
star
8

devolution

🦎 -> 🦖A de-evolution gun for your bundle!
JavaScript
189
star
9

react-locky

"🔒-y" – Asgardian God of Event Scoping 📦, Scroll Locking 📜, Silence Casting 🙊
JavaScript
145
star
10

focus-lock

Gotcha! A11y util for scoping a focus.
TypeScript
139
star
11

vue-focus-lock

It is a trap! A lock for a Focus. A11y util for scoping a focus.
Vue
135
star
12

react-memoize

🧠 React memoization library we all deserve
JavaScript
129
star
13

react-shallow-context

☘️A speed optimization for a Context API
TypeScript
119
star
14

faste

Table based 📦 Finite State Machine 🤖
TypeScript
119
star
15

used-styles

📝All the critical styles you've used to render a page.
TypeScript
115
star
16

use-sidecar

Another way to code splitting
TypeScript
94
star
17

stylelint-semantic-groups

Opinionated rule ordering
TypeScript
92
star
18

beautiful-react-redux

Redux 🚀, Redux 🤘, Redux 🔥 - and the magic optimization
JavaScript
89
star
19

recondition

🤷‍♂️ Declarative render prop based Conditions
TypeScript
77
star
20

proxyequal

There is a bit more smart way to compare things, than a shallow equal.
JavaScript
72
star
21

kashe

A memoization library based on weakmaps. 🤯 Sometimes cache is kashe
TypeScript
63
star
22

react-remove-scroll-bar

Remove document scroll bar. Nothing more
TypeScript
59
star
23

react-scroll-locky

📜🔒 – React full-cream "anti-scroll" library, you were looking for
TypeScript
56
star
24

restate

A redux fractal state library 🕷
JavaScript
55
star
25

dom-focus-lock

It's a Trap! A11y util for scoping a focus.
JavaScript
52
star
26

react-event-injector

💉React way to addEventListener
TypeScript
45
star
27

React-stroller

🚶‍♂️Scroll as you Stroll - the most native custom scrollbars ever made
TypeScript
45
star
28

react-remock

Get any Component replaced. Anywhere. 🛠♻️
TypeScript
44
star
29

multiple-entry-points-example

a npm library with multiple entry points, all typed, all shipped in cjs/esm versions
TypeScript
43
star
30

react-reflexible

🐍 Responsible solution in a flexible form.
TypeScript
43
star
31

aria-hidden

🗣Cast aria-hidden to everything, except...
TypeScript
42
star
32

why-did-you-update-redux

Patch Redux to discover unnecessary re-renders
JavaScript
41
star
33

jsx-compress-loader

⚛️JSX optimisation loader to reduce size of React application
JavaScript
40
star
34

holistic-image

Wholesome image management
TypeScript
37
star
35

theKashey

This is me! 👨‍🔧And all the things I've done. 🧙🏻‍♂️
36
star
36

eslint-plugin-relations

Controls relationships between folders and packages 👩‍💻🤝👮🏻‍♂️
TypeScript
32
star
37

react-gearbox

⚙️📦 Gearbox - Renderless state provisioning and composition
TypeScript
30
star
38

styled-components-mixins

Use popular frameworks with Styled Components
JavaScript
30
star
39

react-on-time

Renderless composable ⏰timers and ⏱intervals
TypeScript
27
star
40

runtime-compress-loader

babel and typescript "runtime" helpers optimization loader
JavaScript
23
star
41

react-queue

⛓ Declarative task scheduler
TypeScript
23
star
42

plimited

👽Promise-based Resource Pool
TypeScript
15
star
43

flow-meter

http(s/2) chunked response meter
TypeScript
14
star
44

react-style-singleton

Simple stylesheet manager for good libraries
TypeScript
14
star
45

use-callback-state

The same `useState`, but it will callback - https://codesandbox.io/s/use-callback-state-fcxtb
TypeScript
14
star
46

require-control

Get the full control over the nodejs module system.
JavaScript
14
star
47

webpack-imported

📝stats-webpack-plugin and 💩webpack-flush-chunks had a baby!
TypeScript
13
star
48

react-rename

Create a renamed duplicate for any Component. For the Fun, and Debug ( sure 🤞)
TypeScript
11
star
49

ts-referent

🤖 project references made not by humans
TypeScript
9
star
50

partial-mock

A solution for ⬆️over and ⬇️under mocking
TypeScript
9
star
51

storybook-include

Folder based storybook configuration for monorepos
TypeScript
9
star
52

react-nyan-stroller

🐈🏳️‍🌈🏳️‍🌈🏳️‍🌈 Nyanyanyanyanyanyanya! (In form of custom scroll bar) https://codesandbox.io/s/j2l71rrxw
TypeScript
9
star
53

quadLoader

загрузчик по пирамиде
8
star
54

react-imported-library

✂Code-split any library using renderProps!
TypeScript
8
star
55

yarn-unlock-file

🧶🔓 keeping things up to date.
TypeScript
8
star
56

read-story-later

Defer storybook's story execution.
JavaScript
7
star
57

react-push-channel

🎈Context is to drill props down. React Push Channel to drill props up.
TypeScript
7
star
58

useReselect

hooks friendly reselect
TypeScript
6
star
59

address-complete

What are you looking for?
JavaScript
6
star
60

idea-exclude

Some your .ideas better be excluded
TypeScript
6
star
61

search-trie

Yet another O(n) trie. This time just and only for string search.
TypeScript
6
star
62

jest-typed-mock

Be safe!. And use the force of TS/Flow to make mocks better.
JavaScript
6
star
63

tileLoader

тайловый загрузчик для yandex2
5
star
64

react-loadable-library

Code-split any library using renderProps!
TypeScript
5
star
65

react-svg-atlas

Combine, isolate, skyrocket the SVG
JavaScript
4
star
66

the-country-names

How to explain the world? Split it into pieces and name the each one.
JavaScript
4
star
67

wipeWebpackCache

JavaScript
3
star
68

css-to-js-loader

Transforms your CSS to the `real` JS
JavaScript
3
star
69

package-self

Place yourself in the right place.
JavaScript
3
star
70

postcss-to-js

A way to create javascript code from css
JavaScript
3
star
71

react-side-channel

To the RenderProps and back.
TypeScript
3
star
72

react-dom-reflection

🔨React meets DOM-API
TypeScript
3
star
73

with-known-usage

👁ES5/ES6 compatible object key usage tracker
TypeScript
3
star
74

proxyquire-webpack-alias

Modification of proxyquire to work with webpack aliases. Proxies commonjs require/es6 import in order to allow overriding dependencies during testing.
JavaScript
3
star
75

restructor

Change your mind 🤯. Face naming standards 🤦‍♀️. Easily rename JavaScript modules 🧙🏻‍♂️.
JavaScript
3
star
76

scan-directory

Multithreaded nodejs directory scan
JavaScript
3
star
77

what-did-i-load

puppeteer based resource investigation
JavaScript
2
star
78

react-fiber-walker

Walks live React Fiber
TypeScript
2
star
79

function-double

Stand in for a function
JavaScript
2
star
80

vue-demi-focus-lock

Vue
2
star
81

derive-svg-components

Derive React components from SVG base
TypeScript
2
star
82

y2FastOverlay

Один из варианта получения скорости при работе с оверлеями напрямую
2
star
83

wipeNodeCache

JavaScript
2
star
84

compare-module-exports

JavaScript
2
star
85

flow-meter-ui

https://flow-meter-ui.vercel.app/
HTML
2
star
86

storybook-addon-poleaxe

a11y (poly) axe
TypeScript
2
star
87

4D

old school C++ stuff
C++
1
star
88

hoist-react-statics

A little helper for a better HOCs
JavaScript
1
star
89

rewiremock-simple-example

testing stuff
JavaScript
1
star
90

maps

различные околокартографические примеры
1
star
91

immutable-changes

Get will found the root change!
JavaScript
1
star
92

gulp-html-prefix

Simple gulp plugin to prefix classNames
JavaScript
1
star