Is React 🚀 FAST, or React is 🐌 SLOW? It's super easy to answer this question, just a no brainer.
- React is fast. Full stop. React is fast when it could be fast: not displaying too much data, and not animating something in 60 frames per second. There is always a faster kid on the block(Svelte?), but React is fast enough almost for any case.
- React is slow. Another full stop. It's not a big deal to make it slow - inline 1000 SVGs and use React to render them all, or render 100 pages simultaneously on a Server Side.
In short - React is 👍 as a Client Side Library, but for Server Side Rendering - oh, it will 🔥 burn your CPU, and AWS bill. Not because it slow, but because everything has limits.
That's obvious - you cannot compare situations when ALL CPUs are working for a ONE customer sake (in browser), and the situating, when you have to render the same, but for a dozen clients simultaneously.
That's also obvious how to mitigate the problem - it was know for generation, and for react itself it’s the main idea around handling unneeded updates - memoization. Or, in terms of old school SSR - just cache.
Cache for small blocks, cache for big blocks, cache for pages, data and intermediate results. Cache as much as possible, and let render results of a one customer speed up render for another customer.
Caching is a well-known pattern, and always was saving a day! For decades! For everyone! Except for React.
React days
So, caching in React? Do you remember an API for it? No, you don’t. There isn’t any.
You still able to cache renderToString
result - not a big deal, or cache renderToNodeStream
as mxstbr did for Spectrum last year.
But do we need caching?
Yes - React 15 was REALLY SLOW, and React 16, even if it is much faster, still require some time to render the result.
Anything requires some time. Everything has requirements and limits.
Caching in React
As long as the problem existed, there were many devs, with a caching background(from previous experiences in different frameworks or languages) to solve it - new patterns and libraries emerged.
Rapscallion
Alternative to React-DOM. Our hero. Saved millions of CPU cycles. Was able to handle data loading on the component level, and caching the result as well.
Worked perfectly. Till React 16. Not worked after.
FormidableLabs / rapscallion
Asynchronous React VirtualDOM renderer for SSR.
Rapscallion
Overview
Rapscallion is a React VirtualDOM renderer for the server. Its notable features are as follows:
- Rendering is asynchronous and non-blocking.
- Rapscallion is roughly 30% faster than
renderToString
. - It provides a streaming interface so that you can start sending content to the client immediately.
- It provides a templating feature, so that you can wrap your component's HTML in boilerplate without giving up benefits of streaming.
- It provides a component caching API to further speed-up your rendering.
Table of Contents
Installation
Using npm:
$ npm install --save rapscallion
In Node.js:
const {
render,
template
} = require("rapscallion");
// ...
API
render
render(VirtualDomNode) -> Renderer
This function returns a Renderer, an interface for rendering your VirtualDOM element. Methods are enumerated below.
Renderer#toPromise
…Another great tool for Component Level caching. Another tool which hacks into React and trying change the way it works. Yet another tool which does not works with React 16.
electrode-io / electrode-react-ssr-caching
Optimize React SSR with profiling and component caching
Support profiling React Server Side Rendering time and component caching to help you speed up SSR.
Installing
npm i electrode-react-ssr-caching
Usage
Note that since this module patches React's source code to inject the caching logic, it must be loaded before the React module.
For example:
import SSRCaching from "electrode-react-ssr-caching";
import React from 'react';
import ReactDOM from 'react-dom/server';
Profiling
You can use this module to inspect the time each component took to render.
import SSRCaching from "electrode-react-ssr-caching";
import { renderToString } from "react-dom/server";
import MyComponent from "mycomponent";
// First you should render your component in a loop to prime the JS engine (i.e: V8 for NodeJS)
for( let i = 0; i < 10; i ++ ) {
renderToString(<MyComponent />);
}
SSRCaching.clearProfileData();
SSRCaching.enableProfiling();
const html
…react-ssr-optimization
A magic system for memoization and templatenization from Walmart Labs. Was once broken during React 14-15 migration, and didn't get React 16 support.
walmartlabs / react-ssr-optimization
React.js server-side rendering optimization with component memoization and templatization
This React Server-side optimization library is a configurable ReactJS extension for memoizing react component markup on the server. It also supports component templatization to further caching of rendered markup with more dynamic data. This server-side module intercepts React's instantiateReactComponent module by using a require()
hook and avoids forking React.
Why we built it
React is a best-of-breed UI component framework allowing us to build higher level components that can be shared and reused across pages and apps. React's Virtual DOM offers an excellent development experience, freeing us up from having to manage subtle DOM changes. Most importantly, React offers us a great out-of-the-box isomorphic/universal JavaScript solution. React's renderToString(..)
can fully render the HTML markup of a page to a string on the server. This is especially important for initial page load performance (particularly for mobile users with low bandwidth) and search engine indexing and ranking…
React-component-caching
TLDR: Actually a working one!
rookLab / react-component-caching
Speedier server-side rendering with component caching in React 16
React Component Caching
Overview
React Component Caching is a component-level caching library for faster server-side rendering with React 16.
- Use any of React's four server-side rendering methods. Rendering is asynchronous.
- Cache components using a simple or template strategy.
- Choose from three cache implementations (LRU, Redis, or Memcached).
Installation
Using npm:
$ npm install react-component-caching
Usage
In Node rendering server:
Instantiate a cache and pass it to any rendering method (renderToString
, renderToStaticMarkup
, renderToNodeStream
, or renderToStaticNodeStream
) as a second argument. Wherever you would use ReactDOM.renderToString
, use ReactCC.renderToString
.
Note: All of these methods are asynchronous, and return a promise. To use them, await
the response before rendering
const ReactCC = require("react-component-caching");
const cache = new ReactCC.ComponentCache();
app.get('/example', async (req,res) => {
const renderString = await ReactCC.
…Citing Speedier Server-Side Rendering in React 16 with Component Caching
Sasha Aickin introduced and demoed component caching in a 2016 talk on ways to speed up server-side rendering. Soon afterwards, Walmart Labs created a component caching library with a profiling feature to time renders. The idea received attention at React Amsterdam last year, and it has surfaced in discussions on Github. A component caching API was also incorporated in Formidable’s React server renderer, Rapscallion.
With the release of React 16 last September, many of these efforts have been due for a reboot. React has improved the speed of its server-side rendering methods and now offers two streaming methods. We were curious if it was possible to harness the power of component caching with these new and improved methods. So we built React Component Caching to give developers the ability to do just that.
But, you know, this library is quite complex inside, and would be broken on next React release - 100%. And user-facing API is also quite complex... But it's working, working with memcache or other caching libraries and was a single saviour... until today.
React-Prerendered-Component
Then - let me introduce another approach to a component level caching in React, which will never be broken. Never by design.
import {CachedLocation, NotCacheable} from 'react-prerendred-component';
const MyCachedComponent = () => (
<CachedLocation cacheKey="MyCachedComponent">
any code you want
</CachedLocation>
);
// component like this shall not be cached.
const SomeNonPureImportantComponent = () => (
<NotCacheable>hey {global.userName}</NotCacheable>
);
Simple? Easy? How does it work?
How it works
-
CachedLocation
will wrap your component with<x-cached-store>{children}<x-cached-store>
- result of
renderToString
would be analyzed for such markers, and marker's content would be moved into the cache. ForrenderToStream
the same is working viatransform streams
. - if
key
was present in the store, thenCachedLocation
would render<x-cached-restore-key />
, and the same post-filter would replace this text by the stored value.
This works on HTML/stream transformation level, and don't rely on ANY React internals.
The same is working for Client Side! CachedLocation
will just wrap your data with a div
and store/restore data using dangerouslySetInnerHTML
. For example, you might cache those big SVGs, I was mentioning in the beginning.
<CachedLocation cacheKey="svg-fileName">
// react component first time, just a string second.
<MySVRGComponent />
</CachedLocation>
renderToString(...);
// - first render, cache is empty, making a full render
// <x-cached-store-1><svg..../></x-cached-store-1>
// - cache stored, extra tags removed
// <svg .../>
renderToString(...);
// - second render, cache exists - restore entity
// <x-cached-restore-1/>
// - we know what to restore
// <svg ...
While
CachedLocation
is adding extra tags during the render - they would be removed later. It's a transparent process!
It is all pure HTML based solution, which works after React did the job.
XSS my friend?
So - let's recall it once again - CachedLocation
will put some tag
in result HTML and some dummy(it is) RegEx will do the rest.
What if your dear customer will do the same? What if the same tag
would be found in JSON, script and visa versa? Of course, it will blow :P
So - in the real world this library would not use so obvious tag names, but will use nanoid to generate unique tag names every render. So - no XSS and no false positives are possible, relax.
<x-cached-supersecretstring-11 /> // it's "safe"
The only, hm, problem here - now caches, generated in different threads, are not compatible with each other, and so in case of distributed cache you have to specify super-secret-seed
by your own.
Templatezation
It's a long, and probably not quite a correct, term stand for less variative memoization.
For example - header of dev.to
contains your avatar, but everything else is the same - you may "template it" into the static and cached <Header/>
. What would happen if you will cache header in a per-user basis? Well, cache is not infinite.
Picture from Scaling React Server-Side Rendering.
Instead of having 100500 different <Button>unuqieText</Button>
you might just have <Button>#text#</Button>
- so you will memoize only complex html markup, adding some "variables" using simple replace
methods.
While using string replaces is not quite safe, this library gives you a few components to inject Placeholders, which uses Context API to drill down the props, and requires just a small code style changes to be used.
const UserHeader = () => (
<div>
.....
<Placeholder name="userName"/>
....
</div>
)
renderToString(<CacheLocation variables={{userName:'Joe'}}><UserHeader/></CacheLocation>);
// <x-cached-store-1 userName="Joe">....{x-cached-placeholder-userName}...</x-cached-store-1>
renderToString(<CacheLocation variables={{userName:'Jack'}}><UserHeader/></CacheLocation>);
// <x-cached-restore-1 userName="Jack"/>
// ....{x-cached-placeholder-userName}...
// ....Jack...
This is a very powerful tactic to reduce memory usage and skyrocket your application via transforming your brand new React App to old school template engine.
Which is just 🚀VERY🚀FAST🚀.
PS: This could give you sub-millisecond SSR, react-component-caching
have proved it.
PPS: But this is not working yet for the client-side cache. Using variables for CSR caching will disable caching.
Another name of this technique is cache interpolation
, and if you want to TRULY understand everything behind fragment caching - read The Article.
Where is the cache?
Another cool moment this approach is a cache
. A simple React 16/Suspense compatible cache model.
- synchronously
get
the cache - synchronously
set
the cache. - No cache available? - Throw a promise!
You cannon use react-cache, as long as it utilizes a different model (there is no set
), but any other cache, including in-memory or shared-memory ones - easy.
But not memcache, as long as asynchronius cache is not supported.
Prerendered component gives you the power of component memoization
, and templatization
both for Server and Client, and would not break in the future, just because it built that way - to never let you down.
Give it a try.
Or just think about it.
theKashey / react-prerendered-component
🤔Partial hydration and caching in a pre-suspense era
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.
- call
- Hydrate the client side
- React-prerendered-component will search for known ids, and
read rendered HTML
back from a page.
- React-prerendered-component will search for known ids, and
- 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…
PS: By the way, react-prerendered-component was a hero for other article:
Top comments (6)
Hello Anton , Very interesting article focusing on Cache. Have you checked Apollo Client to cache on client side. if you are using client side rendering then you can use Apollo client side cache along with graphQL.
That’s apples and oranges.
Apollo cache is about caching data, and then using it to render a view.
This solution is about caching view, but not the data.
You may mix them together, and, get data and view caching simultaneously.
Even more - view layer caching will cut off all nested graphQL connectors and, yeah, 🦄🎉🔥.
TLDR - no, Apollo cache has nothing with this one.
Thanks for correcting on this. How does caching view works? Is this more specific to server side rendering? I have no knowledge about server side rendering and never came across caching view on client side implementation. All my implementations are using reactJS and GatsbyJS to render on client side.
That would take a while, but read Scaling React Server-Side Rendering/Component Caching.
As the old quote said: "There're two things that are hard in Computer Science, it's naming and cache invalidation".
Caching is not hard.
Invalidate cache is hard.
Not only invalidation. Let imagine - you don't have a cached value, so you have generate it. Obviously, that decision would be made in 10 threads at 10 servers simultaneously :)
Cache invalidation is not a real problem - cache management, oh dear, is.