Skip to main content

Overview

COSMOS RSC is built on React Server Components (RSC) and provides a complete framework for building server-rendered React applications. The architecture separates client and server concerns while enabling seamless integration between them.

Core architecture

The framework is divided into three main layers:

Server layer

The server layer handles RSC rendering, routing, and server actions. It runs on Node.js with Express.
core/server/index.js
const app = express();

async function requestHandler(req, res) {
  // Map URL path to page component
  const pagePath = `../../app/pages${req.path}`;
  const Page = require(pagePath).default;
  
  // Create React tree
  const tree = createElement(Page, { searchParams: { ...req.query } });
  
  // Render to RSC stream
  const webpackMap = await getReactClientManifest();
  const rscStream = renderToPipeableStream({ tree }, webpackMap);
  
  // Handle different response types
  if (req.headers.accept === 'text/x-component') {
    // Return RSC payload for client navigations
    res.setHeader('Content-Type', 'text/x-component');
    rscStream.pipe(res);
  } else {
    // Return HTML for initial page loads
    res.setHeader('Content-Type', 'text/html');
    // Process through Fizz worker for HTML streaming
  }
}
Key responsibilities:
  • File-system based routing
  • React Server Components rendering
  • Server action execution
  • Cookie management
  • HTML streaming with Suspense

Client layer

The client layer hydrates the initial HTML and handles client-side navigation.
core/client/index.js
import { hydrateRoot } from 'react-dom/client';
import { createFromReadableStream } from 'react-server-dom-webpack/client';

async function hydrateDocument() {
  // Parse RSC payload from embedded stream
  const { rootLayout, tree, formState } = await createFromReadableStream(
    rscStream,
    { callServer }
  );
  
  // Cache initial page
  routerCache.set(getFullPath(window.location.href), tree);
  
  // Hydrate React tree
  const app = (
    <ErrorBoundary>
      <BrowserApp rootLayout={rootLayout} initialState={{ tree }} />
    </ErrorBoundary>
  );
  
  hydrateRoot(document, app, { formState });
}
Key responsibilities:
  • Hydrating server-rendered HTML
  • Client-side navigation
  • Router cache management
  • Server action invocation

Build layer

The build layer uses Webpack to bundle client code and generate manifests.
core/build/webpack.config.js
module.exports = {
  entry: [
    path.resolve(__dirname, '../client/index.js'),
    path.resolve(__dirname, '../../app/globals.css'),
  ],
  output: {
    path: path.resolve(__dirname, '../../.cosmos-rsc'),
    filename: 'client.js',
  },
  plugins: [
    new ReactServerWebpackPlugin({
      isServer: false,
      clientReferences: [
        {
          directory: './app',
          recursive: true,
          include: /\.js$/,
        },
      ],
    }),
  ],
};
Key responsibilities:
  • Bundling client JavaScript
  • Processing CSS with Tailwind
  • Generating React Client Manifest
  • Identifying client components

RSC rendering pipeline

The rendering pipeline coordinates between server and client:
  1. Request handling: Express receives request and maps URL to page component
  2. RSC rendering: React renders components to RSC stream format
  3. HTML streaming: Fizz worker converts RSC stream to HTML
  4. Payload injection: RSC data embedded into HTML as inline scripts
  5. Client hydration: Browser parses RSC payload and hydrates React tree
  6. Interactive: Client-side navigation and server actions work seamlessly

Worker threads

COSMOS RSC uses worker threads to enable concurrent HTML rendering:
core/server/lib/fizz-worker.js
const { parentPort } = require('worker_threads');

parentPort.on('message', async (request) => {
  // Consume RSC stream
  const { rootLayout, tree } = await createFromNodeStream(
    htmlConsumerRSCStream,
    serverConsumerManifest
  );
  
  // Render to HTML
  const htmlStream = renderToPipeableStream(
    createElement(SSRApp, { initialState: { tree }, rootLayout }),
    {
      bootstrapScripts: ['/client.js'],
      onShellReady: () => {
        htmlStream
          .pipe(injectRSCPayload(payloadConsumerRSCStream))
          .pipe(writableStream);
      },
    }
  );
});
The Fizz worker:
  • Runs in a separate thread from the main server
  • Consumes the RSC stream
  • Renders to HTML using React DOM Server
  • Injects RSC payload into the HTML

Client/server split

Components are automatically split between client and server:
// Runs only on server - no 'use client' directive
async function UserProfile() {
  const userData = await fetchUserData();
  return <div>{userData.name}</div>;
}
The ReactServerWebpackPlugin identifies client components and generates a manifest mapping module IDs to bundled chunks.

Communication patterns

Server to client

Server components pass data to client components through props:
async function ServerParent() {
  const data = await fetchData();
  return <ClientChild data={data} />;
}

Client to server

Client components call server actions to execute server code:
'use client';

function ClientForm() {
  async function handleSubmit() {
    const result = await serverAction();
  }
  return <button onClick={handleSubmit}>Submit</button>;
}
Server actions use the callServer function which dispatches through the app reducer to make a POST request to the server.

Directory structure

Your application code lives in the app/ directory:
app/
├── pages/          # File-system routes
├── actions/        # Server action modules
├── components/     # Shared components
├── root-layout.js  # Root HTML layout
└── globals.css     # Global styles
The framework code lives in core/:
core/
├── server/         # Server runtime
├── client/         # Client runtime
├── build/          # Build configuration
└── rsc-html-stream/ # Stream utilities

Next steps

Routing

Learn about file-system routing

Server Components

Understand React Server Components

Build docs developers (and LLMs) love