react_server
Transport-neutral SSR and server-function runtime for React Dart.
The package contains the server-function context and registry, the VM-side SSR worker client, and the Node-side React renderer. It has no Shelf or Routed dependency.
Installation
dependencies:
react_server: ^0.1.0
Choose a separate HTTP adapter:
dependencies:
react_server_routed: ^0.1.0
# or
react_server_shelf: ^0.1.0
Server functions
Create one registry and let generated code populate it:
import 'package:my_app/.generated/server_actions.g.dart';
import 'package:react_server/react_server.dart';
final actions = ServerFunctionRegistry();
void registerActions() {
registerServerActions(registry: actions);
}
Each handler receives a ServerFunctionContext containing request metadata,
authentication state, request headers, deadlines, and cancellation. Concrete
server adapters translate their request type into this context.
SSR worker client
The Dart VM server communicates with the generated Node worker through
ReactSsrClient:
final ssr = ReactSsrClient(
endpoint: Uri.parse('http://127.0.0.1:3001/'),
);
final document = await ssr.render(
component: 'package:my_app/lib/app.dart#App',
props: {'title': 'Dashboard'},
);
Relative endpoints are resolved only against the baseUri supplied to
render or renderStream. When using a relative endpoint, supply a trusted
absolute origin; do not derive it from an untrusted request Host header.
Using an absolute endpoint avoids the need for a base URI.
ReactSsrDocument contains rendered HTML and serialized props. The HTTP
adapter injects both into the configured index template.
On JavaScript runtimes, the same API uses the platform fetch function. This
allows a routed_node Node, Bun, or Cloudflare Fetch handler to call a
separate SSR service without trying to start a subprocess:
final ssr = ReactSsrClient(
endpoint: Uri.parse('https://ssr.example.com/'),
);
The endpoint must implement the JSON contract generated by react_tool. The
current generated SSR entrypoint is Node-specific (node:http, node:stream,
and node:module), so it cannot be bundled directly into a Cloudflare Worker
yet. A practical edge deployment is:
Cloudflare Worker (routed_node + react_server_routed)
├─ static assets and React actions
└─ fetch -> regional Node SSR worker (generated by react_tool)
For an experimental Fetch-compatible renderer, configure the SSR build with:
ssr:
runtime: fetch
That emits a module-style endpoint using React's
renderToReadableStream. The target is separate from the Node listener and
can be deployed as its own Worker. Streaming through the React server adapter
is still pending for this target.
Caching and lifecycle primitives
ReactDocumentCache is an in-process document cache. When documents must be
shared between processes or survive restarts, provide a ReactDocumentStore to
the selected HTTP adapter. The store receives the document TTL, stale-while-
revalidate window, and cache tags, so a Redis, database, disk, or edge-KV
implementation can preserve the same application contract.
For a single-host deployment, FileReactDocumentStore provides a restart-safe
JSON-on-disk implementation. ReactRouteManifest can supply route-specific
TTL, stale windows, and tags:
final app = ReactServerApp(
// ...
documentStore: FileReactDocumentStore(Directory('var/react-cache')),
routeManifest: ReactRouteManifest.fromJson({
'routes': [
{
'pattern': '/news/:slug',
'ttlSeconds': 30,
'staleWhileRevalidateSeconds': 300,
'tags': ['news'],
},
],
}),
);
Use a database, Redis, or edge-KV implementation of ReactDocumentStore when
the application runs on more than one host.
ReactDataCache provides typed data caching with concurrent-load
deduplication, stale-while-revalidate, and tag invalidation:
final data = ReactDataCache();
final user = await data.getOrLoad<User>(
'user:$id',
() => loadUser(id),
ttl: const Duration(minutes: 5),
tags: ['user:$id'],
);
Server functions can schedule non-critical work with
ServerFunctionContext.scheduleAfterResponse. The Routed and Shelf adapters
drain these callbacks after the action handler completes. Deployment-specific
durable background-work guarantees remain the responsibility of the host.
Partial prerendering
Use ReactPartialDocument when a route has a cacheable shell and dynamic
regions with different lifetimes. The shell must contain a marker for each
region:
ReactPartialDocument(
shellKey: 'dashboard-shell',
shell: () => '<main><!--react-partial:summary--></main>',
regions: [
ReactPartialRegion(
key: 'summary',
ttl: const Duration(seconds: 15),
render: () => renderSummaryHtml(),
),
],
);
Pass the descriptor through partialDocument on ReactServerApp or
RoutedReactApplication. The adapters retain one ReactDataCache for the
application and resolve the shell and every region independently. A short
region TTL therefore does not invalidate the shell or unrelated regions.
Node renderer entrypoint
The application's lib/ssr.dart is compiled to JavaScript. It imports hidden
generated factories and registries, registers component builders, and calls
registerGlobalRenderer. The generated Node runtime then invokes
renderToString inside a real React render stack, preserving hooks, contexts,
foreign components, suspense, refs, memoization, and error boundaries.
Package boundary
- HTTP request/response handling belongs in
react_server_routedorreact_server_shelf. - Protocol annotations and browser clients belong in
react_actions. - Build and worker process orchestration belongs in
react_tool. - Test fixtures belong in
react_testingand compose with the selectedserver_testingadapter.
Libraries
- react_server
- Portable server-side runtime for React SSR and server function dispatch.