• Stars
    star
    106
  • Rank 325,871 (Top 7 %)
  • Language
    TypeScript
  • License
    MIT License
  • Created over 2 years ago
  • Updated almost 2 years ago

Reviews

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

Repository Details

React 18 Streaming. Full-fledged & Easy.

React Streaming

react-streaming

React 18 Streaming. Full-fledged & Easy.

Follow: Twitter > @brillout
Chat: Discord > Cubes > hashreact-streaming

Unfamiliar with React 18 Streaming? Check out Dan's article about SSR and Streaming.

⚠️ While react-streaming is stable in itself (it's used in production and has good CI test coverage), note that React's SSR streaming support is still early and that the React team is working on high-level APIs that will make parts of react-streaming obsolete, see @sebmarkbage comment at "RFC: injectToStream".

Contents

Intro

Features (for React users)

  • Unlocks <Suspense> for SSR apps.
  • useAsync(): easily fetch data for SSR apps.
  • Two SEO strategies: conservative or google-speed.
  • Seamless support for Node.js (serverless) platforms (Vercel, AWS EC2, ...) and Edge platforms (Cloudflare Workers, Deno Deploy, Netlify Edge, Vercel Edge, ...).
  • Easy error handling.

Features (for library authors)

  • useAsync(): add data fetching capabilities to your library. High-level and easy to use.
  • injectToStream(): inject chunks to the stream for your library. Low-level and difficult to use, but highly flexible.

Easy

import { renderToStream } from 'react-streaming/server'
const {
  pipe, // Node.js (Vercel, AWS EC2, ...)
  readable // Edge (Coudflare Workers, Deno Deploy, Netlify Edge, Vercel Edge, ...)
} = await renderToStream(<Page />)

Why Streaming

React 18's new SSR streaming architecture unlocks many capabilities:

  • Easily fetch data for SSR apps.
  • Fundamentally improved mobile performance. (Mobile users can progressively load the page as data is fetched, before even a single line of JavaScript is loaded. Especially important for users with a low-end device and users with a poor internet connection.)
  • Progressive Hydration. (Page is interactive before even the page has finished loading.)

Problem: the current React 18 Streaming architecture is low-level and difficult to use.

Solution: react-streaming.

react-streaming makes it easy to build the libraries of tomorrow, for example:

  • Use Telefunc to easily fetch data for your Next.js app or your Vite + vite-plugin-ssr app. (Replacing Next.js's getServerSideProps() and vite-plugin-ssr's onBeforeRender().)
  • Better GraphQL tools, e.g. Vilay.

Get Started

  1. Install

    npm install react-streaming
  2. Server-side

    import { renderToStream } from 'react-streaming/server'
    const {
      pipe, // Defined if running in Node.js, otherwise `null`
      readable // Defined if running on the Edge (.e.g. Coudflare Workers), otherwise `null`
    } = await renderToStream(<Page />)

That's it.

Options

const options = {
  // ...
}
await renderToStream(<Page />, options)
  • options.disable?: boolean: Disable streaming.

    <Page> is still rendered to a stream, but the promise const promise = renderToStream() resolves only after the stream has finished. (This effectively disables streaming from a user perspective, while unlocking React 18 Streaming capabilities such as SSR <Supsense>.)

  • options.seoStrategy?: 'conservative' | 'google-speed'

    • conservative (default): Disable streaming if the HTTP request originates from a bot. (Ensuring bots to always see the whole HTML.)

    • google-speed: Don't disable streaming for the Google Bot.

      • Pro: Google ranks your website higher because the initial HTTP response is faster. (To be researched.)
      • Con: Google will likely not wait for the whole HTML, and therefore not see it. (To be tested.)
    • Custom SEO strategy: use options.disable. For example:

      // Always stream, even for bots:
      const disable = false
      
      // Disable streaming for bots, except for the Google Bot and some other bot:
      const disable =
        isBot(userAgent) &&
        !['googlebot', 'some-other-bot'].some(n => userAgent.toLowerCase().includes(n))
      
      await renderToStream(<Page />, { disable })
  • options.userAgent?: string: The HTTP User-Agent request header. (Needed for options.seoStrategy.)

  • options.webStream?: boolean: Use Web Streams instead of Node.js Streams in Node.js. (Node.js 18 released Web Streams support.)

  • options.onBoundaryError?: (err: unknown) => void: Called when a <Suspense> boundary fails. See Error Handling.

  • const { streamEnd } = await renderToStream(<Page />)
    // βœ… Page Shell succesfully rendered.
    const success: boolean = await streamEnd
    // Stream ended.
    if (success) {
      // βœ… <Page> succesfully rendered
    } else {
      // ❌ A <Suspense> boundary failed.
    }

    Note that streamEnd never rejects.

    ⚠️ Read Error Handling before using streamEnd. In particular, do not use success to change the behavior of your app/stream (because React automatically takes care of gracefully handling <Suspense> failures).

Error Handling

The promise await renderToStream() resolves after the page shell is rendered. This means that if an error occurs while rendering the page shell, then the promise rejects with that error.

πŸ“– The page shell is the set of all components before <Suspense> boundaries.

try {
  await renderToStream(<Page />)
  // βœ… Page shell succesfully rendered and is ready in the stream buffer.
} catch(err) {
  // ❌ Something went wrong while rendering the page shell.
}

The stream returned by await renderToStream() doesn't emit errors.

πŸ“– If an error occurs during the stream, then that means that a <Suspense> boundary failed. Instead of emiting a stream error, React swallows the error on the server-side and retries to resolve the <Suspense> boundary on the client-side. If the <Suspense> fails again on the client-side, then the client-side throws the error.

This means that errros occuring during the stream are handled by React and there is nothing for you to do on the server-side. That said, you may want to gracefully handle the error on the client-side e.g. with react-error-boundary.

You can use options.onBoundaryError() for error tracking purposes.

useAsync()

import { useAsync } from 'react-streaming'

function Page({ movieId }) {
  return (
    <Suspense fallback={<p>Loading...</p>}>
      <Movie id={movieId}/>
    </Suspense>
  )
}

async function fetchMovie(id) {
  const response = await fetch(`https://star-wars.brillout.com/api/films/${id}.json`)
  return response.json()
}

// This component is isomorphic: it works on both the client-side and server-side.
// The data fetched while SSR is automatically passed and re-used on the client for hydration.
function Movie({ id }) {
  const key = [
    'star-wars-movies',
    id // Re-run `fetchMovie()` if `id` changes
  ]
  const movie = useAsync(key, () => fetchMovie(id))
  return (
    <ul>
      <li>
        Title: {movie.title}
      </li>
      <li>
        Release Date: {movie.release_date}
      </li>
    </ul>
  )
}

See useAsync() (Library Authors) for more information.


Get Started (Library Authors)

react-streaming enables you to suspend React rendering and await something to happen. (Usually data fetching.) The novelty here is that it's isomorphic:

  • It works on the client-side, as well as on the server-side (while Serve-Side Rendering).
  • For hydration, data is passed from the server to the client. (So that data isn't loaded twice.)

You have the choice between three methods:

  • useAsync(): High-level and easy.
  • injectToStream(): Low-level and highly flexible (useAsync() is based on it). Easy & recommended for injecting script and style tags. Complex for data fetching (if possible, use useAsync() instead).

useAsync() (Library Authors)

This section is a low-level description of useAsync(). For a high-level description, see useAsync() instead.

import { useAsync } from 'react-streaming'

function SomeComponent() {
  const someAsyncFunc = async function () {
    const value = 'someData'
    return value
  }
  const key = ['some', 'invalidating', 'values']
  // `useAsync()` suspends rendering until the promise returned by `someAsyncFunc()` resolves.
  const value = useAsync(key, someAsyncFunc)
  assert(value === 'someData')
}

When <SomeComponent> is rendered on the server-side (SSR), it injects the resolved value into the stream and the client-side picks up the injected value. This means that the client-side doesn't call someAsyncFunc(): instead, the client-side re-uses the value resolved on the server-side.

If you want someAsyncFunc() to be re-run, then change key. The someAsyncFunc() is only re-run if when the component is un-mounted and re-mounted, or if key changes. For example, changing the state of your component (e.g. with useState()) will not re-run someAsyncFunc() if you provide the same key.

Usually the key is set to ['name-of-the-function', ...functionArguments].

You can think of key to serve a similar purpose to React Queries's key, and to the deps argument of React's useEffect(fn, deps).

injectToStream()

injectToStream(chunk: string | Buffer | unknown, options?: { flush?: boolean }) enables you to inject chunks to the current stream.

There are two ways to access injectToStream():

  1. With renderToStream():
    import { renderToStream } from 'react-streaming/server'
    const { injectToStream } = await renderToStream(<Page />)
  2. With useStream():
    import { useStream } from 'react-streaming'
    
    function SomeComponent() {
      const stream = useStream()
      if (stream === null) {
        // No stream available. This is the case:
        // - On the client-side.
        // - When `option.disable === true`.
        // - When react-streaming is not installed.
      }
      const { injectToStream } = stream
    }

Usage examples:

// Inject JavaScript (e.g. for progressive hydration)
injectToStream('<script type="module" src="/main.js"></script>', { flush: true })

// Inject CSS (e.g. for CSS-in-JS)
injectToStream('<styles>.some-component { color: blue }</styles>', { flush: true })

// Pass data to client
injectToStream(`<script type="application/json">${JSON.stringify(someData)}</script>`)

For a full example of using injectToStream(), have a look at useAsync()'s implementation.

If setting options.flush to true, then the stream will be flushed after chunk has been written to the stream. This is only applicable for Node.js streams and only if you are using a compression library that makes a flush() method available. For example, compression adds a res.flush() method. The option is ignored if there isn't a flush() method available.

More Repositories

1

awesome-react-components

Curated List of React Components & Libraries.
37,355
star
2

awesome-angular-components

Catalog of Angular 2+ Components & Libraries
3,236
star
3

telefunc

Remote Functions. Instead of API.
TypeScript
649
star
4

awesome-redux

Catalog of Redux Libraries & Learning Material
374
star
5

wildcard-api

Functions as API.
JavaScript
367
star
6

awesome-universal-rendering

Awesome resources about server side sendering (SSR), static rendering, pre-rendering, static site generators (SSG).
339
star
7

vite-plugin-mdx

Vite Plugin for MDX
TypeScript
112
star
8

goldpage

Page Builder.
JavaScript
57
star
9

awesome-web-apps

Curated List of Web Apps
56
star
10

timer-tab

Source code of Timer Tab
JavaScript
48
star
11

forge-sha256

SHA-256 implementation extracted from digitalbazaar/forge
JavaScript
44
star
12

clocktab

Source code of Clock Tab.
JavaScript
34
star
13

gulp-jspm

gulp plugin to build assets loaded with jspm/SystemJS
JavaScript
34
star
14

awesome-frontend-libraries

Catalog of Frontend UI Libraires
34
star
15

awesome-vue-refactor

A curated list of awesome things related to Vue.js
33
star
16

vike

Build Your Own Framework.
TypeScript
31
star
17

json-serializer

Same as JSON but with added support for Date, undefined, NaN, Infinity, RegExp and more.
JavaScript
25
star
18

parcel-ssr

SSR tool. Tiny yet powerful. Based on Parcel.
JavaScript
15
star
19

extendable-error-class

Extend the Error Class with ES2015/ES5/Babel, i.e. fix for `class MyError extends Error{}`
JavaScript
14
star
20

test-javascript-hash-implementations

Javascript hash implementations tested for speed. Including SHA-256, MD5, CRC32, SHA3, BLAKE2S
JavaScript
12
star
21

proto-db

JavaScript Object as Database.
JavaScript
11
star
22

vite-plugin-server-entry

Handles the server entry.
TypeScript
8
star
23

promise-serial

Run promises in series
JavaScript
8
star
24

vike-with-vercel

JavaScript
8
star
25

docpress

TypeScript
8
star
26

vite-plugin-ssr_windi-css

Example of using vite-plugin-ssr with Windi CSS
JavaScript
7
star
27

build-worker

Build your Cloudflare Workers with esbuild.
JavaScript
7
star
28

vite-to-vike

Add SSR/SSG to an existing Vite app.
JavaScript
7
star
29

reprop

A JavaScript library to manage the logics of views
JavaScript
6
star
30

vps-deno

Example of using vite-plugin-ssr with Deno
JavaScript
6
star
31

vite-plugin-ssr_tailwind-css

JavaScript
6
star
32

html

Generate HTML documents.
JavaScript
6
star
33

FasterWeb

Proposal to reduce loading time of library code on the web
5
star
34

libraries

Curated list of library lists
5
star
35

research

5
star
36

fetch

WHATWG Fetch for the browser and Node.js
JavaScript
5
star
37

esm-cjs-interop-playground

JavaScript
4
star
38

vite-plugin-ssr_react-europe_talk

vite-plugin-ssr talk
JavaScript
4
star
39

timerlog

A small utility to measure and log time
JavaScript
4
star
40

stem

TypeScript
4
star
41

vite-plugin-ssr_vercel_build-output-api

JavaScript
4
star
42

vike-with-svelte

JavaScript
4
star
43

import

TypeScript
3
star
44

vite-plugin-ssr-vuetify

JavaScript
3
star
45

url-props

Fully-featured URL parsing, for Node.js & Browser.
JavaScript
3
star
46

vike-react-router

JavaScript
3
star
47

vps_prerender-worker

JavaScript
3
star
48

vike-with-solid-ssr

TypeScript
3
star
49

stem-react

React renderer for vite-plugin-ssr
TypeScript
3
star
50

part-regex

Create a RegExp by defining some parts with strings and other parts with RegExp.
TypeScript
3
star
51

website-dependency-tree

Retrieve the Dependency Tree of a Website
JavaScript
3
star
52

vite-framework

Example of a framework built on top of Vite + vite-plugin-ssr
TypeScript
3
star
53

vike-with-redux

JavaScript
3
star
54

test-e2e

TypeScript
2
star
55

cloudflare-workers-react-18-streaming

Example of using React 18 SSR Streaming on Cloudflare Workers
JavaScript
2
star
56

vike-with-redux_minimal-example

JavaScript
2
star
57

eject

TypeScript
2
star
58

vite-plugin-ssr_vue-router

JavaScript
2
star
59

goldpage-react-express-starter

πŸš€ Starter to create a full-stack app: Interactive Frontend (React) + SSR (Goldpage) + Server (Node.js, Express).
JavaScript
2
star
60

vite-3-react-18-ssr-streaming

JavaScript
2
star
61

libframe

TypeScript
2
star
62

libassert

Tiny zero-dependency tool for library authors to create assertion functions with clean strack traces.
TypeScript
2
star
63

wildcard-intercept

Library to intercept Wildcard API calls.
JavaScript
2
star
64

tab-utils

Utilities for Clock Tab and Timer Tab
TypeScript
2
star
65

set-text-dimensions

set block of text dimension to a given width and height
JavaScript
2
star
66

vps-mui

TypeScript
2
star
67

vike-preact-server-routing

JavaScript
2
star
68

vike-with-solid-spa

TypeScript
2
star
69

find

Find a file among your project files.
JavaScript
2
star
70

get-parent-dirname

Get the path of the directory of the parent module.
JavaScript
2
star
71

clean-sentence

Clean a sentence from missing first letter upper case, trailing dot and/or remove URLs, emojis, white space doublets.
JavaScript
2
star
72

vite-config-json

TypeScript
2
star
73

free-emailing

Free emailing including custom domain and programmatic API
2
star
74

mdocs

Utility to create documentation for programming libraries with markdown.
JavaScript
2
star
75

vite-plugin-ssr_vercel-2022-02

JavaScript
2
star
76

trace-logs

Trace console.logS in Node.js to know from which file they come from
JavaScript
2
star
77

npm-download-count

Retrieve package's number of downloads in a period of time
JavaScript
2
star
78

vps-framework

JavaScript
2
star
79

nuxt-telefunc

TypeScript
1
star
80

pnpm-vue-demo

1
star
81

restack

TypeScript
1
star
82

require-shim

JavaScript
1
star
83

mini-assert

a mini(malistic) assertion library for both the Browser and Node.js
JavaScript
1
star
84

get-user-dir

For libraries to get the directory of the user's code
JavaScript
1
star
85

framework-builder

TypeScript
1
star
86

telefunc-vercel

JavaScript
1
star
87

submodule-init

TypeScript
1
star
88

vite-webpack

TypeScript
1
star
89

vite-pr-12030-reprod

CSS
1
star
90

release-me

Publish your npm packages.
TypeScript
1
star
91

vps-mdi

TypeScript
1
star
92

jspm-plugin-audio

JSPM Audio Plugin
JavaScript
1
star
93

vite-swc

JavaScript
1
star
94

vike-with-urql

TypeScript
1
star
95

vike-react-simple

JavaScript
1
star
96

github-action-matrix-with-js

JavaScript
1
star
97

vps-babel-bug

JavaScript
1
star
98

vps_import.meta.hot

JavaScript
1
star
99

vps-reprod-308

TypeScript
1
star
100

node-mini-log

mini(malistic) logs
JavaScript
1
star