Research
Security News
Threat Actor Exposes Playbook for Exploiting npm to Build Blockchain-Powered Botnets
A threat actor's playbook for exploiting the npm ecosystem was exposed on the dark web, detailing how to build a blockchain-powered botnet.
react-streaming
Advanced tools
react-streaming
React 18 Streaming. Full-fledged & Easy.
Contents
Features (for React users):
<Suspsense>
for SSR apps.conservative
or google-speed
.useAsync()
hook.Features (for library authors):
useSsrData()
: Define isomorphic data.injectToStream()
: Inject chunks to the stream.Easy:
import { renderToStream } from 'react-streaming/server'
const {
pipe, // Node.js (Vercel, AWS)
readable // Edge Platforms (Coudflare Workers, Netlify Edge, Deno Deploy)
} = await renderToStream(<Page />)
React 18's new SSR streaming architecture unlocks many capabilities:
getServerSideProps()
nor vite-plugin-ssr
's onBeforeRender()
.)The problem: The current React 18 Streaming architecture is low-level and its ergonomics are cumbersome. (E.g. there is no standard way for library authors to take advantage of the new streaming architecture.)
The solution: react-streaming
.
Install
npm install react-streaming
Server-side
import { renderToStream } from 'react-streaming/server'
const {
pipe, // Defined if running in Node.js, otherwise `null`
readable // Defined if running in Edge Platforms (.e.g. Coudflare Workers), otherwise `null`
} = await renderToStream(<Page />, options)
Client-side
import { ReactStreaming } from 'react-streaming/client'
// Wrap your root component `<Page>` (aka `<App>`) with `<ReactStreaming>`
const page = (
<ReactStreaming>
<Page />
</ReactStreaming>
)
options.disable?: boolean
: Disable streaming.
<Page>
is still rendered to a stream, but the promiseconst 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.
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.)
If an error occurs at the beginning of the stream then await renderToStream()
rejects with the error.
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.
}
Technically speaking, the page shell is composed of all components that are outside a <Suspsense>
boundary.
If an error occurs later in the stream (i.e. a suspense boundary fails), then React swallows the error on the server-side and retries to resolve the suspense boundary on the client-side. If the <Suspsense>
fails again on the client-side, then the client-side throws the error.
useAsync()
import { useAsync } from 'react-streaming'
function StarWarsMovies() {
return (
<div>
<p>List of Star Wars movies:</p>
<Suspense fallback={<p>Loading...</p>}>
<MovieList />
</Suspense>
</div>
)
}
// 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 MovieList() {
const movies = useAsync(async () => {
const response = await fetch('https://star-wars.brillout.com/api/films.json')
return response.json()
})
return (
<ul>
{movies.forEach((movie) => (
<li>
{movie.title} ({movie.release_date})
</li>
))}
</ul>
)
}
react-streaming
enables you to suspend React rendering and await something to happen. (Usually data fetching.)
The novelty here is that it's isomorphic:
You have the choice between three methods:
useAsync()
: Highest-level & easiest.useSsrData()
: High-level & easy.injectToStream()
: Low-level and highly flexible (both useAsync()
and useSsrData()
are based on it). Easy & recommended for injecting script and style tags. Complex for data fetching (if possible, use useSsrData()
or useAsync()
instead).useAsync()
For how to use useAsync()
, see example above.
useSsrData()
import { useSsrData } from 'react-streaming'
function SomeComponent() {
const key = 'some-unique-key'
const someAsyncFunc = async function () {
const value = 'someData'
return value
}
// `useSsrData()` suspends rendering until the promise returned by `someAsyncFunc()` resolves.
const value = useSsrData(key, someAsyncFunc)
assert(value === 'someData')
}
If <SomeComponent>
is rendered only on the client-side, then useSsrData()
is essentially a
cache that never invalidates. (If you want to re-run someAsyncFunc()
, then change the key.)
If <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. (So that the
client-side doesn't call someAsyncFunc()
but, instead, re-uses the value resolved on
the server-side.)
This is for example how useAsync()
is implemented:
import { useId } from 'react'
import { useSsrData } from 'react-streaming'
function useAsync(asyncFn) {
const id = useId()
return useSsrData(id, asyncFn)
}
injectToStream()
injectToStream(htmlChunk: string)
allows you to inject strings to the current stream.
There are two ways to access injectToStream()
:
renderToStream()
:
import { renderToStream } from 'react-streaming/server'
const { injectToStream } = await renderToStream(<Page />)
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>')
// Inject CSS (e.g. for CSS-in-JS)
injectToStream('<styles>.some-component { color: blue }</styles>')
// Pass data to client
injectToStream(`<script type="application/json">${JSON.stringify(someData)}</script>`)
For a full example of using injectToStream()
, have a look at useSsrData()
's implementation.
FAQs
React 18 Streaming. Full-fledged & Easy.
The npm package react-streaming receives a total of 5,033 weekly downloads. As such, react-streaming popularity was classified as popular.
We found that react-streaming demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
A threat actor's playbook for exploiting the npm ecosystem was exposed on the dark web, detailing how to build a blockchain-powered botnet.
Security News
NVD’s backlog surpasses 20,000 CVEs as analysis slows and NIST announces new system updates to address ongoing delays.
Security News
Research
A malicious npm package disguised as a WhatsApp client is exploiting authentication flows with a remote kill switch to exfiltrate data and destroy files.