Skip to content

Server APIs

Fresh

React Server Components and Server Actions enable server-side rendering and data fetching patterns.

Server Components

Server Components run on the server and are not included in the JavaScript bundle. They can directly access server-side resources:

jsx
// This component runs on the server
async function NotesPage() {
  const notes = await db.notes.findAll();
  return (
    <div>
      {notes.map(note => (
        <Note key={note.id} note={note} />
      ))}
    </div>
  );
}

Rules for Server Components

  • Cannot use state (useState, useReducer)
  • Cannot use effects (useEffect, useLayoutEffect)
  • Cannot use browser-only APIs
  • Can use async/await
  • Can directly call databases, file systems, and internal services

'use client' Directive

Marks a file as a Client Component:

jsx
'use client';

import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

The 'use client' directive must be at the top of the file, before any imports. It marks the boundary between Server and Client components.

Serializable Props

Props passed from Server to Client Components must be serializable:

  • Primitives (string, number, boolean, null, undefined)
  • Arrays and plain objects containing serializable values
  • Date, Map, Set (serialized and deserialized)
  • Server Actions (functions marked with 'use server')
  • JSX elements

Not serializable: Functions (except Server Actions), classes, DOM nodes

'use server' Directive

Marks a server-side function that can be called from client-side code:

jsx
// actions.js
'use server';

export async function createNote(formData) {
  const title = formData.get('title');
  await db.notes.create({ title });
}
jsx
// ClientComponent.jsx
'use client';
import { createNote } from './actions';

export default function NoteForm() {
  return (
    <form action={createNote}>
      <input name="title" />
      <button type="submit">Create</button>
    </form>
  );
}

Security

Server Actions are public HTTP endpoints. Always validate and authorize:

jsx
'use server';

export async function deleteNote(noteId) {
  const user = await getAuthenticatedUser();
  if (!user) throw new Error('Unauthorized');

  const note = await db.notes.find(noteId);
  if (note.userId !== user.id) throw new Error('Forbidden');

  await db.notes.delete(noteId);
}

Server Rendering APIs

renderToPipeableStream (Node.js)

Renders a React tree to a pipeable Node.js stream with streaming Suspense support:

jsx
import { renderToPipeableStream } from 'react-dom/server';

app.get('/', (req, res) => {
  const { pipe } = renderToPipeableStream(<App />, {
    bootstrapScripts: ['/client.js'],
    onShellReady() {
      res.statusCode = 200;
      res.setHeader('Content-Type', 'text/html');
      pipe(res);
    },
    onError(error) {
      console.error(error);
      res.statusCode = 500;
    }
  });
});

renderToReadableStream (Edge/Web)

For edge runtimes (Cloudflare Workers, Deno):

jsx
import { renderToReadableStream } from 'react-dom/server';

async function handler(request) {
  const stream = await renderToReadableStream(<App />, {
    bootstrapScripts: ['/client.js']
  });
  return new Response(stream, {
    headers: { 'Content-Type': 'text/html' }
  });
}

renderToString (Legacy)

Renders to a string. Does not support streaming or Suspense:

jsx
import { renderToString } from 'react-dom/server';

const html = renderToString(<App />);

WARNING

renderToString does not support streaming or waiting for data. Prefer renderToPipeableStream or renderToReadableStream.

Static Site Generation

prerenderToNodeStream

Pre-renders React to a static Node.js stream, waiting for all data to load:

jsx
import { prerenderToNodeStream } from 'react-dom/static';

async function generateStaticPage() {
  const { prelude } = await prerenderToNodeStream(<App />, {
    bootstrapScripts: ['/client.js']
  });
  // prelude is a Node.js Readable stream of the complete HTML
}