• Stars
    star
    677
  • Rank 66,694 (Top 2 %)
  • Language
    TypeScript
  • License
    MIT License
  • Created over 5 years ago
  • Updated 8 months ago

Reviews

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

Repository Details

React ESI: Blazing-fast Server-Side Rendering for React and Next.js

React ESI: Blazing-fast Server-Side Rendering for React and Next.js

Build Status Coverage Status npm version MIT Licence

React ESI is a super powerful cache library for vanilla React and Next.js applications, that can make highly dynamic applications as fast as static sites. It provides a straightforward way to boost your application's performance by storing fragments of server-side rendered pages in edge cache servers. It means that after the first rendering, fragments of your pages will be served in a few milliseconds by servers close to your end users! It's a very efficient way to improve the performance and the SEO of your websites; and to dramatically reduce both your hosting costs and the energy consumption of these applications. Help the planet, use React ESI!

Because it is built on top of the Edge Side Includes (ESI) W3C specification, React ESI natively supports most of the well-known cloud cache providers including Cloudflare Workers, Akamai and Fastly. Of course, React ESI also supports the open source Varnish cache server that you can use in your own infrastructure for free (configuration example).

Also, React ESI allows to specify a different Time To Live (TTL) per React component and to generate the corresponding HTML asynchronously using a secure (signed) URL. The cache server fetches and stores in the cache all the needed fragments (the HTML corresponding to every React component), builds the final page and sends it to the browser. React ESI also allows components to (re-)render client-side without any specific configuration.

ESI example

Schema from The Varnish Book

Discover React ESI in depth with this presentation

Examples

Install

Using Yarn:

$ yarn add react-esi

Or using NPM:

$ npm install react-esi

Usage

React ESI provides a convenient Higher Order Component that will:

  • replace the wrapped component by an ESI tag server-side (don't worry React ESI also provides the tooling to generate the corresponding fragment);
  • render the wrapped component client-side, and feed it with the server-side computed props (if any).

React ESI automatically calls a static async method named getInitialProps() to populate the initial props of the component. Server-side, this method can access to the HTTP request and response, for instance, to set the Cache-Control header, or some cache tags.

These props returned by getInitialProps() will also be injected in the server-side generated HTML (in a <script> tag). Client-side the component will reuse the props coming from the server (the method will not be called a second time). If the method hasn't been called server-side, then it will be called client-side the first time the component is mounted.

The Higher Order Component

// pages/index.js
import React from 'react';
import withESI from 'react-esi';
import MyFragment from 'components/MyFragment';

const MyFragmentESI = withESI(MyFragment, 'MyFragment');
// The second parameter is an unique ID identifying this fragment.
// If you use different instances of the same component, use a different ID per instance.

const Index = () => (
  <div>
    <h1>React ESI demo app</h1>
    <MyFragmentESI greeting="Hello!" />
  </div>
);
// components/MyFragment.js
import React from 'react';

export default class MyFragment extends React.Component {
  render() {
    return (
      <section>
        <h1>A fragment that can have its own TTL</h1>

        <div>{this.props.greeting /* access to the props as usual */}</div>
        <div>{this.props.dataFromAnAPI}</div>
      </section>
    );
  }

  static async getInitialProps({ props, req, res }) {
    return new Promise(resolve => {
      if (res) {
        // Set a TTL for this fragment
        res.set('Cache-Control', 's-maxage=60, max-age=30');
      }

      // Simulate a delay (call to a remote service such as a web API)
      setTimeout(
        () =>
          resolve({
            ...props, // Props coming from index.js, passed through the internal URL
            dataFromAnAPI: 'Hello there'
          }),
        2000
      );
    });
  }
}

The initial props must be serializable using JSON.stringify(). Beware Map, Set and Symbol!

Note: for convenience, getInitialProps() has the same signature than the Next.js one. However, it's a totally independent and standalone implementation (you don't need Next.js to use it).

Serving the Fragments

To serve the fragments, React ESI provides a ready to use controller compatible with Express:

// server.js
import express from 'express';
import { path, serveFragment } from 'react-esi/lib/server';

const server = express();
server.use((req, res, next) => {
  // Send the Surrogate-Control header to announce ESI support to proxies (optional with Varnish, depending of your config)
  res.set('Surrogate-Control', 'content="ESI/1.0"');
  next();
});

server.get(path, (req, res) =>
  // "path" default to /_fragment, change it using the REACT_ESI_PATH env var
  serveFragment(
    req,
    res,
    // "fragmentID" is the second parameter passed to the "WithESI" HOC, the root component used for this fragment must be returned
    fragmentID => require(`./components/${fragmentID}`).default) 
);

// ...
// Other Express routes come here

server.listen(80);

Alternatively, here is a full example using a Next.js server:

// server.js
import express from 'express';
import next from 'next';
import { path, serveFragment } from 'react-esi/lib/server';

const port = parseInt(process.env.PORT, 10) || 3000
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()

app.prepare().then(() => {
  const server = express();
  server.use((req, res, next) => {
    // Send the Surrogate-Control header to announce ESI support to proxies (optional with Varnish)
    res.set('Surrogate-Control', 'content="ESI/1.0"');
    next();
  });

  server.get(path, (req, res) =>
    serveFragment(req, res, fragmentID => require(`./components/${fragmentID}`).default)
  );
  server.get('*', handle); // Next.js routes

  server.listen(port, err => {
    if (err) throw err;
    console.log(`> Ready on http://localhost:${port}`);
  });
});

Features

  • Support Varnish, Cloudflare Workers, Akamai, Fastly and any other cache systems having ESI support
  • Written in TypeScript
  • Next.js-friendly API

Environment Variables

React ESI can be configured using environment variables:

  • REACT_ESI_SECRET: a secret key used to sign the fragment URL (default to a random string, it's highly recommended to set it to prevent problems when the server restart, or when using multiple servers)
  • REACT_ESI_PATH: the internal path used to generate the fragment, should not be exposed publicly (default: /_fragment)

Passing Attributes to the <esi:include> Element

To pass attributes to the <esi:include> element generated by React ESI, pass a prop having the following structure to the HOC:

{
  esi: {
    attrs: {
      alt: "Alternative text",
      onerror: "continue"
    }
  }
}

Troubleshooting

The Cache is Never Hit

By default, most cache proxies, including Varnish, never serve a response from the cache if the request contains a cookie. If you test using localhost or a similar local domain, clear all pre-existing cookies for this origin. If the cookies are expected (e.g.: Google Analytics or ad cookies), then you must configure properly your cache proxy to ignore them. Here are some examples for Varnish.

Design Considerations

To allow the client-side app to reuse the props fetched or computed server-side, React ESI injects <script> tags containing them in the ESI fragments. After the assembling of the page by the cache server, these script tags end up mixed with the legit HTML. These tags are automatically removed from the DOM before the rendering phase.

Going Further

React ESI plays very well with advanced cache strategies including:

  • Cache invalidation (purge) with cache tags (Varnish / Cloudflare)
  • Warming the cache when data change in the persistence layer (Varnish)

Give them a try!

Vue.js / Nuxt

We love Vue and Nuxt as much as React and Next, so we're a currently porting React ESI for this platform. Contact us if you want to help!

Credits

Created by Kévin Dunglas. Sponsored by Les-Tilleuls.coop.

More Repositories

1

frankenphp

🧟 The modern PHP app server
Go
6,849
star
2

vulcain

🔨 Fast and idiomatic client-driven REST APIs.
Go
3,513
star
3

mercure

An open, easy, fast, reliable and battery-efficient solution for real-time communications
Go
3,349
star
4

symfony-docker

A Docker-based installer and runtime for Symfony. Install: download and `docker compose up`.
Dockerfile
2,354
star
5

doctrine-json-odm

An object document mapper for Doctrine ORM using JSON types of modern RDBMS.
PHP
525
star
6

DunglasActionBundle

Symfony controllers, redesigned
PHP
258
star
7

phpdoc-to-typehint

Add scalar type hints and return types to existing PHP projects using PHPDoc annotations
PHP
226
star
8

DunglasAngularCsrfBundle

Automatic CSRF protection for JavaScript apps using a Symfony API
PHP
151
star
9

vaccin.click

Une extension Firefox pour trouver et réserver automatiquement votre créneau de vaccination COVID-19.
JavaScript
94
star
10

php-torcontrol

PHP TorControl, a library to control TOR
PHP
86
star
11

php-socialshare

Server-side social networks share counts and share links retriever
PHP
80
star
12

DunglasTodoMVCBundle

A TodoMVC implementation wrote with Symfony, Chaplin.js and Backbone.js
PHP
78
star
13

solid-client-php

PHP library for accessing data and managing permissions on data stored in a Solid Pod
PHP
59
star
14

frankenphp-demo

Demo app for FrankenPHP
HTML
59
star
15

httpsfv

A Go library to parse and serialize HTTP structured field values
Go
57
star
16

symfonycon-lisbon

A joind.in clone built with Symfony 4 and Vue.js
PHP
39
star
17

demo-vulcain-api-platform

Use API Platform with the Vulcain protocol and Varnish!
JavaScript
34
star
18

php-to-json-schema

Creates a JSON Schema from a PHP class
PHP
32
star
19

stack2slack

A Slack bot to monitor StackOverflow/StackExchange tags
Go
29
star
20

frankenphp-wordpress

WordPress on FrankenPHP
Dockerfile
29
star
21

php-property-info

Retrieve type and description of PHP properties using various sources
PHP
27
star
22

prestashop-html5-theme

HTML5 Prestashop tempate enhanced for SEO with Google Rich Snippets support
Smarty
27
star
23

DunglasDigitalOceanBundle

DigitalOcean API v2 client for Symfony and API Platform
PHP
25
star
24

frankenphp-drupal

Drupal on FrankenPHP
Dockerfile
23
star
25

blog-api

A demonstration blog API for the API Platform framework
PHP
22
star
26

stripe-invoice-exporter

Download all your Stripe PDF invoices in bulk.
PHP
20
star
27

jquery.confirmExit

jQuery confirm before exit plugin
JavaScript
16
star
28

calavera

A (static) Single Page Application generator using Markdown files
Go
16
star
29

ShopApiPlugin

PHP
16
star
30

kdDoctrineGuardFacebookConnectPlugin

Facebook Connect symfony plugin (extends sfGuardPlugin)
PHP
16
star
31

demo-postgres-listen-notify

Demo of the PostgreSQL LISTEN/NOTIFY support in Symfony Messenger
PHP
14
star
32

DunglasTorControlBundle

Integration of PHP TorControl library in Symfony
PHP
13
star
33

blog-client

A demonstration blog client for the API Platform framework
ApacheConf
12
star
34

symfony-demo-mercure

A demo project using Symfony's Mercure integration
PHP
10
star
35

docker-private-composer-packages

Example: Securely Access Private Composer Packages
Dockerfile
9
star
36

uri-template-tester

Test if a URI matches a given URI template (RFC6570)
HTML
8
star
37

planning

Planning management written in Symfony2 and Doctrine 2
PHP
8
star
38

symfony-lock

Symfony lock
8
star
39

piy

A modern self-managed Content Management System
PHP
7
star
40

api-parallelism-benchmark

A benchmark comparing HTTP/2 Server Push to GraphQL-Like compound documents
HTML
6
star
41

Elgg-profile_friendlyurl

Creates friendly URLs for user's Elgg profiles as subdomains.
PHP
6
star
42

forumphp2016

PHP
6
star
43

api-platform-heroku

Helpers to use API Platform and Symfony applications on Heroku.
PHP
5
star
44

Elgg-fblink

Link a Facebook and an Elgg account
PHP
5
star
45

dunglas

My GitHub profile!
5
star
46

php-basics

Cours de PHP (en français)
JavaScript
5
star
47

Elgg-presence

Friends Online on Elgg, Facebook and Twitter
PHP
4
star
48

frankenphp-website

The website of FrankenPHP
HTML
4
star
49

Elgg-twitterlogin

Login to Elgg using Twitter
PHP
4
star
50

Signal-TLS-Proxy

Dockerfile
3
star
51

vclient-web

Web interface for Viessmann Vito heating system
Python
3
star
52

debian-hosting

Automatically exported from code.google.com/p/debian-hosting
Python
3
star
53

Elgg-groups_bookmarkswidget

Elgg plugin to display a bookmarks widget in groups homepages
PHP
3
star
54

workshop-mercure

Code produced during my Mercure workshop
PHP
3
star
55

mercure-reproducer-cors

Reproducer for CORS issues with the Mercure.rocks Hub
HTML
3
star
56

slides-sfLive-2015

Slides de ma présentation au Symfony Live 2015
JavaScript
3
star
57

php-documention-generator

3
star
58

workshop-panther

Code produced during by Panther workshop
PHP
2
star
59

demo-autowiring

Symfony autowiring demo
PHP
2
star
60

slides-sfPot-2015-07-10

Slides: using PSR-7 with the Symfony framework
JavaScript
2
star
61

.github

My GitHub files
2
star
62

api-platform-crud-demo

JavaScript
2
star
63

slides-sfPot-2015-01-15

API-first et Linked Data avec Symfony
JavaScript
2
star
64

symfony-psr7-benchmark

Benchmark PSR-7 support in Symfony
PHP
1
star
65

ajax-syntax-highlighter

Automatically exported from code.google.com/p/ajax-syntax-highlighter
JavaScript
1
star
66

selfpublish

Automatically exported from code.google.com/p/selfpublish
PHP
1
star
67

training-apr-19

PHP
1
star
68

test-again

Vue
1
star
69

symfony-4-0

Symfony 4.0 benchmark on phpbenchmarks.com
PHP
1
star
70

scoopeo

Automatically exported from code.google.com/p/scoopeo
1
star
71

easyubuntu

Automatically exported from code.google.com/p/easyubuntu
1
star
72

serializer-pack

1
star
73

panther-legacy

Legacy releases of symfony/panther (including old versions of ChromeDriver and Geckodriver)
PHP
1
star
74

platform-on-platform-workshop

#APIDays: API Platform on Platform.sh workshop
JavaScript
1
star
75

crypt-manager

Automatically exported from code.google.com/p/crypt-manager
Python
1
star