@anansi/core

The itsy bitsy spider crawled up the water spout.
Down came the rain, and washed the spider out.
Out came the sun, and dried up all the rain,
and the itsy bitsy spider went up the spout again
React 19 framework with streaming SSR support, built on a composable "spouts" architecture.
Installation
yarn add @anansi/core
Quick Start
Anansi uses a dual-entry pattern for SSR: one entry for the server and one for the client.
yarn start-anansi ./src/index.tsx
This automatically uses ./src/index.tsx for the client and ./src/index.server.tsx for the server.
Concepts
Spouts
Spouts are composable middleware for building React applications. They handle concerns like routing, data fetching, document structure, and more.
- Server:
laySpouts() - Lays out the spouts for SSR, streaming React to the response
- Client:
floodSpouts() - Hydrates the application on the client
The spout pattern allows you to compose functionality in a declarative, nested structure where each spout can:
- Inject props to downstream spouts
- Wrap the rendered application with providers
- Serialize data for hydration
Entry Points
Client Entry (index.tsx)
import { useController } from '@data-client/react';
import {
floodSpouts,
documentSpout,
dataClientSpout,
routerSpout,
JSONSpout,
appSpout,
navigatorSpout,
} from '@anansi/core';
import App from './App';
import { createRouter } from './routing';
const spouts = documentSpout({ title: 'My App' })(
JSONSpout()(
navigatorSpout()(
dataClientSpout()(
routerSpout({ useResolveWith: useController, createRouter })(
appSpout(<App />),
),
),
),
),
);
floodSpouts(spouts);
Server Entry (index.server.tsx)
import { useController } from '@data-client/react';
import {
laySpouts,
documentSpout,
dataClientSpout,
prefetchSpout,
routerSpout,
JSONSpout,
appSpout,
navigatorSpout,
} from '@anansi/core/server';
import App from './App';
import { createRouter } from './routing';
const spouts = prefetchSpout('controller')(
documentSpout({ title: 'My App' })(
JSONSpout()(
navigatorSpout()(
dataClientSpout()(
routerSpout({ useResolveWith: useController, createRouter })(
appSpout(<App />),
),
),
),
),
),
);
export default laySpouts(spouts);
Spouts Reference
appSpout
The innermost spout that wraps your React application.
import { appSpout } from '@anansi/core';
appSpout(<App />)
documentSpout
Generates the HTML document structure with proper asset loading.
import { documentSpout } from '@anansi/core';
documentSpout({
title: 'My App',
head?: ReactNode,
lang?: string,
rootId?: string,
charSet?: string,
csPolicy?: CSPolicy,
})
Content Security Policy
The csPolicy option configures CSP headers. In production, it sets Content-Security-Policy; in development, it uses Content-Security-Policy-Report-Only.
const csPolicy = {
'base-uri': "'self'",
'object-src': "'none'",
'script-src': ["'self'", "'unsafe-inline'"],
'style-src': ["'unsafe-inline'", "'self'"],
};
documentSpout({ title: 'My App', csPolicy })
Nonces are automatically injected into script-src for inline scripts.
routerSpout
Integrates @anansi/router for client-side navigation.
import { routerSpout } from '@anansi/core';
routerSpout({
useResolveWith: () => any,
createRouter: (history) => RouteController,
onChange?: (update, callback) => void,
})
Provides to downstream spouts:
matchedRoutes - Array of matched route objects
router - RouteController instance
searchParams - URLSearchParams from the current URL
dataClientSpout
Integrates @data-client/react for data fetching with automatic SSR hydration.
import { dataClientSpout } from '@anansi/core';
dataClientSpout({
getManagers?: () => Manager[],
})
Server-side, it creates a persisted store and serializes state for hydration. Client-side, it hydrates from the serialized state.
JSONSpout
Serializes data from upstream spouts into <script type="application/json"> tags for hydration.
import { JSONSpout } from '@anansi/core';
JSONSpout({
id?: string,
})
navigatorSpout
Provides browser navigator-like properties (language preferences) that work on both server and client.
import { navigatorSpout, useNavigator } from '@anansi/core';
navigatorSpout()
function MyComponent() {
const { language, languages } = useNavigator();
}
prefetchSpout (Server Only)
Prefetches route data before rendering. Must wrap routerSpout.
import { prefetchSpout } from '@anansi/core/server';
prefetchSpout('controller')(
)
This calls route.resolveData() and route.component.preload() for all matched routes before SSR.
antdSpout (Ant Design)
Integrates Ant Design's CSS-in-JS with SSR support.
import { antdSpout } from '@anansi/core/antd';
import { antdSpout } from '@anansi/core/antd/server';
antdSpout()
Core Functions
laySpouts (Server)
Wraps spouts for server-side rendering with streaming support.
import { laySpouts } from '@anansi/core/server';
export default laySpouts(spouts, {
timeoutMS?: number,
onError?: (error) => void,
});
Returns a render function compatible with Express: (clientManifest, req, res) => Promise<void>
floodSpouts (Client)
Hydrates the application on the client.
import { floodSpouts } from '@anansi/core';
floodSpouts(spouts, {
rootId?: string,
});
CLI Commands
start-anansi
Development server with hot module replacement for both client and server.
yarn start-anansi ./src/index.tsx
Features:
- Dual webpack compilation (client + server)
- In-memory filesystem for fast rebuilds
- Hot reloading for both bundles
- Automatic SSR on all non-asset routes
- Proxy support from webpack devServer config
serve-anansi
Production server for pre-built applications.
yarn serve-anansi ./dist-server/App.js
Scripts API
For programmatic usage:
import { serve, devServe } from '@anansi/core/scripts';
serve(entry, options?)
Starts a production server.
serve('./dist-server/App.js', {
serveAssets?: boolean,
serveProxy?: boolean,
});
Environment Variables:
PORT - Server port (default: 8080)
WEBPACK_PUBLIC_PATH - Public path for assets
Options
serveAssets | boolean | Serve static assets from the build output. Useful for local validation; use a dedicated HTTP server in production. |
serveProxy | boolean | Enable proxying based on webpack devServer config. Useful for local validation; use a reverse proxy in production. |
devServe(entry, env?)
Starts a development server with HMR.
devServe('./src/index.tsx', {
});
Types
ServerProps
Props available to server-side spouts:
interface ServerProps {
req: Request | IncomingMessage;
res: Response | ServerResponse;
clientManifest: StatsCompilation;
nonce: string;
}
Spout Types
import type { Spout, ServerProps, NavigatorProperties } from '@anansi/core';
CSPolicy
Content Security Policy configuration:
interface CSPolicy {
[directive: string]: string | string[];
}
Exports
@anansi/core
Client-side exports:
floodSpouts - Hydrate the application
documentSpout - Document structure
dataClientSpout - Data Client integration
routerSpout - Router integration
JSONSpout - JSON serialization for hydration
appSpout - Application wrapper
navigatorSpout - Navigator properties
useNavigator - Hook for navigator properties
@anansi/core/server
Server-side exports (all client exports plus):
laySpouts - SSR render function
prefetchSpout - Route data prefetching
CSPolicy - CSP type
@anansi/core/scripts
Build/dev scripts:
serve - Production server
devServe - Development server
@anansi/core/antd
Ant Design integration (client-side).
@anansi/core/antd/server
Ant Design integration (server-side).