Skip to content
Back to skills

React Server

ASecurity

Build applications with @lazarv/react-server — a React Server Components runtime built on Vite. Covers use directives, file-system router, HTTP hooks, caching, live components, workers, MCP, deployment, and all core APIs.

  • 444 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added June 5, 2026
developmentgoreactnodedockerawsazuregitapidatabasefrontend

Works with

  • cli
  • api
  • mcp

Security analysis

A100/100

Scanned June 5, 2026

npx -y skills add lazarv/react-server --skill react-server --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of React Server?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for React Server
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/lazarv-react-server/badge)](https://www.skillsdirectory.com/skills/lazarv-react-server)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: react-server
description: Build applications with @lazarv/react-server — a React Server Components runtime built on Vite. Covers use directives, file-system router, HTTP hooks, caching, live components, workers, MCP, deployment, and all core APIs.
---

You are working on a project that uses `@lazarv/react-server` — an open React Server Components runtime (not a framework). It is built on the Vite Environment API and supports Node.js, Bun, and Deno.

Full documentation: https://react-server.dev
Any docs page as markdown: https://react-server.dev/{path}.md
JSON Schema for configuration: https://react-server.dev/schema.json
Examples: https://github.com/lazarv/react-server/tree/main/examples

When you need more detail on a specific topic, fetch the relevant markdown page from the docs site.

## CLI Commands

```sh
# Development (with explicit entrypoint)
pnpm react-server ./App.jsx

# Development (file-system router, no entrypoint needed)
pnpm react-server

# Production build
pnpm react-server build [./App.jsx]

# Start production server
pnpm react-server start

# Useful flags
# --open          Open browser on start
# --host 0.0.0.0  Listen on all interfaces
# --port 3001     Custom port
# --https         Enable HTTPS
```

## Use Directives

These directives go at the top of a file or inside a function body (lexically scoped RSC):

- `"use client"` — Client component (enables React hooks, event handlers, browser APIs)
- `"use server"` — Server function (callable from client, receives FormData as first arg)
- `"use live"` — Live component (async generator that yields JSX, streams updates via WebSocket)
- `"use worker"` — Worker module (offloads to Worker Thread on server, Web Worker on client)
- `"use cache"` — Cached function with options: `"use cache; ttl=200; tags=todos"` or `"use cache; profile=todos"` or `"use cache: file; tags=todos"` or `"use cache: request"` (per-request dedup) or `"use cache: request; no-hydrate"` (no browser hydration)
- `"use dynamic"` — Force dynamic rendering (opt out of static/prerender)
- `"use static"` — Force static rendering at build time
- `"use hydrate"` — Hydration island: render a server subtree as HTML immediately, write a separate island RSC payload, and hydrate it later as a local non-root outlet. Syntax: `"use hydrate: visible; rootMargin=0px; threshold=0.2; id=counter"`.

## Hydration Islands

Use hydration islands when the page root should stay server-only but a selected subtree needs deferred interactivity. Put the directive inside a server component function body:

```jsx
function CounterIsland() {
  "use hydrate: visible; rootMargin=0px; threshold=0.2; id=counter";

  return <Counter />;
}
```

Key behavior:
- The island is SSR-rendered immediately.
- The runtime emits request-scoped hydration data for the island and can omit the main `PAGE_ROOT` RSC payload when no page-root hydration is needed.
- The client hydrates the island as a local outlet, not as a remote component.
- Hydration islands are created during initial HTML rendering; if a `"use hydrate"` component appears in a later RSC update payload, it renders as normal React content because the parent tree already owns it.
- Components inside the island can use `Link local` and `Refresh local` to navigate or refresh only the island outlet.
- DevTools shows islands in the Outlets panel with an `island` badge and hydrated/not hydrated state.

Strategies:
- `load` — hydrate as soon as the client entry runs and the island payload is available. This is the default for `"use hydrate"` and is best for above-the-fold controls that should become interactive immediately while still using a local outlet.
- `idle` — hydrate through `requestIdleCallback`, falling back to `setTimeout`. Supports `timeout` in milliseconds; default is `2000`.
- `visible` — hydrate when an `IntersectionObserver` sees the island marker. Supports `rootMargin` (default `600px`) and `threshold` (default `0`). If `IntersectionObserver` is unavailable, hydrate immediately.
- `interaction` — hydrate on user interaction. Default events are `pointerenter`, `focusin`, `pointerdown`, and `click`; override with `events=pointerenter,focusin`. The triggering event starts hydration and should not be treated as replayed into the hydrated component.
- `media` — hydrate when `matchMedia(query)` matches. If the query already matches, hydrate immediately; otherwise listen for changes. Missing `query` or unavailable `matchMedia` falls back to immediate hydration. Use this for motion preferences too, e.g. `query=(prefers-reduced-motion: no-preference)` or `query=(prefers-reduced-motion: reduce)`.
- `never` — render static HTML without creating a hydration payload. Client component effects and handlers inside the island never run.

## File-System Router Conventions

When no entrypoint is passed to the CLI, the file-system router is used. Default root is `src/pages` (configurable via `root` in config).

```
src/pages/
├── layout.jsx              # Root layout (wraps all routes)
├── page.jsx                # Index page (/)
├── about.jsx               # /about (any .jsx/.tsx file is a page)
├── (group)/                # Route group (no URL segment)
│   └── page.jsx
├── users/
│   ├── page.jsx            # /users
│   ├── [id].page.jsx       # /users/:id (dynamic segment)
│   └── [id]/
│       └── posts.page.jsx  # /users/:id/posts
├── docs/
│   └── [...slug].page.jsx  # /docs/* (catch-all, slug is string[])
├── @sidebar/               # Parallel route / outlet named "sidebar"
│   └── page.jsx
├── loading.page.jsx        # Loading fallback (Suspense boundary)
├── error.page.jsx          # Error fallback (ErrorBoundary)
├── index.middleware.mjs    # Middleware for this route segment
├── GET.posts.server.mjs    # API route: GET /posts
└── mcp.server.mjs          # MCP endpoint at /mcp
```

Key conventions:
- `page.jsx` or `index.jsx` — route page (index route)
- `"use client"` page — client-only route (no server round-trip, state preserved between navigations)
- `layout.jsx` — wraps child routes with persistent layout
- `[param]` — dynamic route parameter (passed as prop)
- `[...slug]` — catch-all (param is `string[]`)
- `[[...slug]]` — optional catch-all
- `(name)` — route group / transparent segment (not in URL)
- `@name/` — parallel route outlet
- `*.middleware.{js,mjs,ts,mts}` — middleware
- `*.server.{js,mjs,ts,mts}` — API route handler
- `GET.*.server.mjs` / `POST.*.server.mjs` — HTTP method-specific API route
- `{filename.xml}.server.mjs` — escaped route segment for special characters
- `*.resource.{js,mjs,ts,mts}` — resource file (auto-bound to route)

Route validation (export `validate` object with `params`/`search` schemas from any page):

```tsx
import { z } from "zod";
import { user } from "@lazarv/react-server/routes";

export const validate = {
  params: z.object({ id: z.string().regex(/^\d+$/) }),
};

export default user.createPage(({ id }) => <h1>User {id}</h1>);
```

Auto-generated routes module (`@lazarv/react-server/routes`): exports named route descriptors for every route. Each has `.Link`, `.href()`, `.useParams()`, `.useSearchParams()`, `.createPage()`, `.createLayout()`, `.createLoading()`, `.createError()`, `.createMiddleware()`.

## Imports Quick Reference

```js
// Core hooks and utilities
import {
  useHttpContext,
  useUrl,
  usePathname,
  useSearchParams,
  useRequest,
  useResponse,
  useFormData,
  headers,
  cookie,
  setCookie,
  deleteCookie,
  status,
  redirect,
  rewrite,
  after,
  useCache,
  useResponseCache,
  withCache,
  revalidate,
  invalidate,
  getRuntime,
} from "@lazarv/react-server";

// Configuration helper
import { defineConfig } from "@lazarv/react-server/config";

// Navigation (client-side)
import { Link, Refresh, ReactServerComponent, ScrollRestoration, useNavigate } from "@lazarv/react-server/navigation";
import { useClient } from "@lazarv/react-server/navigation";
// useClient() returns { navigate, replace, prefetch }

// Typed router
import { createRoute, createRouter, Route, SearchParams } from "@lazarv/react-server/router";

// Resources (typed, schema-validated data fetching)
import { createResource, createResources, resources } from "@lazarv/react-server/resources";

// Auto-generated routes module (file-system router only)
import { index, about, user } from "@lazarv/react-server/routes";

// Error handling
import { ErrorBoundary } from "@lazarv/react-server/error-boundary";

// Client utilities
import { ClientOnly } from "@lazarv/react-server/client";

// Micro-frontends
import RemoteComponent from "@lazarv/react-server/remote";

// Workers
import { isWorker } from "@lazarv/react-server/worker";

// MCP (Model Context Protocol)
import { createServer, createTool, createResource as createMcpResource, createPrompt } from "@lazarv/react-server/mcp";
```

## Typed Router

`@lazarv/react-server` includes a fully typed routing solution with compile-time type safety for route paths, params, and search params. No code generation step — works with any schema library (Zod, ArkType, Valibot) or lightweight parse functions.

### Defining routes with createRoute

```ts
import { createRoute } from "@lazarv/react-server/router";
import { z } from "zod";

// Static route
export const home = createRoute("/", { exact: true });

// Dynamic route — params extracted from pattern
export const user = createRoute("/user/[id]", {
  exact: true,
  validate: { params: z.object({ id: z.coerce.number().int().positive() }) },
});

// Search params with validation
export const products = createRoute("/products", {
  exact: true,
  validate: {
    search: z.object({
      sort: z.enum(["name", "price", "rating"]).catch("name"),
      page: z.coerce.number().int().positive().catch(1),
    }),
  },
});

// Lightweight parse (no schema library)
export const post = createRoute("/post/[slug]", {
  exact: true,
  parse: { params: { slug: String }, search: { tab: String } },
});

// Fallback routes
export const notFound = createRoute("*");
export const userNotFound = createRoute("/user/*"); // scoped fallback
```

### Composing routes with createRouter (server)

```tsx
import { createRoute, createRouter } from "@lazarv/react-server/router";
import * as routes from "./routes";

const router = createRouter({
  home: createRoute(routes.home, <Home />),
  user: createRoute(routes.user, <UserPage />),
  notFound: createRoute(routes.notFound, <NotFound />),
});

export default function App() {
  return (
    <div>
      <nav>
        <router.home.Link>Home</router.home.Link>
        <router.user.Link params={{ id: 42 }}>User 42</router.user.Link>
      </nav>
      <router.Routes />
    </div>
  );
}
```

### Client-only routes

Routes without server components — useful for tabs, modals, filters. Define with `createRoute` (no element) and use in `"use client"` components:

```tsx
"use client";
import { settings } from "./routes";

export default function SettingsPage() {
  const params = settings.useParams();
  return (
    <nav>
      <settings.Link params={{ tab: "profile" }}>Profile</settings.Link>
      <settings.Link params={{ tab: "security" }}>Security</settings.Link>
    </nav>
  );
}
```

In the file-system router, any page with `"use client"` at the top is automatically a client-only route.

### Typed hooks and Link

Every route descriptor provides: `.Link` (typed Link component), `.href(params?)` (URL builder), `.useParams()` (typed params or null), `.useSearchParams()` (typed search params), `.SearchParams` (route-scoped transform boundary).

### Functional search param updaters

```tsx
<products.Link search={(prev) => ({ ...prev, page: prev.page + 1 })}>
  Next Page
</products.Link>
```

### SearchParams transform boundary

Bidirectional URL ↔ app search param transforms with `decode` and `encode`:

```tsx
<products.SearchParams decode={decode} encode={encode}>
  {children}
</products.SearchParams>
```

### Programmatic navigation

```tsx
import { useNavigate } from "@lazarv/react-server/navigation";
const navigate = useNavigate();
navigate(user, { params: { id: 42 } });
```

## Resources (Typed Data Fetching)

Schema-validated, route-aware data fetching with `"use cache"` as the caching runtime.

```ts
import { createResource } from "@lazarv/react-server/resources";
import { z } from "zod";

// Define + bind loader
export const userById = createResource({
  key: z.object({ id: z.coerce.number().int().positive() }),
}).bind(async ({ id }) => {
  "use cache";
  return db.users.findById(id);
});

// Singleton (no key)
export const currentUser = createResource().bind(async () => {
  return (await getSession()).user;
});
```

Using in components:

```tsx
// .use() — suspense-integrated, auto-re-renders on invalidation
const userData = userById.use({ id });

// .query() — imperative, returns Promise
const user = await userById.query({ id: 42 });

// .prefetch() — warm cache
userById.prefetch({ id: 42 });
```

Route-resource bindings for automatic pre-loading:

```ts
const todosMapping = todos.from(({ search }) => ({ filter: search.filter ?? "all" }));
// Pass to createRoute: createRoute(routes.todos, <TodosPage />, { resources: [todosMapping] })
```

## Scroll Restoration

Enable via config or component:

```js
// react-server.config.mjs
export default { scrollRestoration: true };
// Or with options:
export default { scrollRestoration: { behavior: "smooth" } };
```

```tsx
import { ScrollRestoration } from "@lazarv/react-server/navigation";
<ScrollRestoration behavior="smooth" />
```

Handles: forward navigation (scroll to top or #hash), back/forward (restore saved position), page refresh (zero-flash restore), query-only changes (preserve position), nested scroll containers, `prefers-reduced-motion` support.

## Component Patterns

### Server Component (default — no directive needed)

```jsx
// Server components can be async and access backend resources directly
export default async function Page() {
  const data = await db.query("SELECT * FROM posts");
  return <ul>{data.map(post => <li key={post.id}>{post.title}</li>)}</ul>;
}
```

### Client Component

```jsx
"use client";

import { useState } from "react";

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

### Server Function

```jsx
// File-level directive
"use server";

export async function createPost(formData) {
  const title = formData.get("title");
  await db.insert({ title });
}
```

```jsx
// Or inline in a server component
export default function Page() {
  async function handleSubmit(formData) {
    "use server";
    await db.insert({ title: formData.get("title") });
  }
  return (
    <form action={handleSubmit}>
      <input name="title" />
      <button type="submit">Create</button>
    </form>
  );
}
```

### Live Component (real-time streaming)

```jsx
"use live";

export default async function* LiveClock() {
  while (true) {
    yield <div>{new Date().toLocaleTimeString()}</div>;
    await new Promise(r => setTimeout(r, 1000));
  }
}
```

### Worker Function

```jsx
"use worker";

// All exports must be async functions
export async function fibonacci(n) {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
}
```

### Lexically Scoped RSC (inline directives)

```jsx
// No file-level directive — this is a server component
export default async function Page() {
  const data = await fetchData();

  // Inline client component — extracted at build time, no separate file needed
  function InteractiveList({ items }) {
    "use client";
    const [filter, setFilter] = useState("");
    return (
      <>
        <input value={filter} onChange={e => setFilter(e.target.value)} />
        <ul>{items.filter(i => i.includes(filter)).map(i => <li key={i}>{i}</li>)}</ul>
      </>
    );
  }

  return <InteractiveList items={data} />;
}
```

Server → Client → Server nesting is also supported:

```jsx
async function getData() {
  "use server";

  function Display({ value }) {
    "use client";
    const [show, setShow] = useState(false);
    return <button onClick={() => setShow(!show)}>{show ? value : "Reveal"}</button>;
  }

  const value = await db.getSecret();
  return <Display value={value} />;
}
```

## HTTP Hooks

Available in server components, middlewares, and API routes:

```jsx
import {
  useUrl,           // URL object
  usePathname,      // string
  useSearchParams,  // URLSearchParams
  useRequest,       // Request object
  useResponse,      // Response object
  useFormData,      // FormData (POST requests)
  useHttpContext,   // full context object
  headers,          // headers(name?) to read, headers({ key: value }) to set
  cookie,           // cookie(name?) to read, cookie(name, value, opts) to set
  setCookie,        // setCookie(name, value, opts)
  deleteCookie,     // deleteCookie(name)
  status,           // status(code, statusText?)
  redirect,         // redirect(url, statusCode?)
  rewrite,          // rewrite(url)
  after,            // after(callback) — runs after response is sent
} from "@lazarv/react-server";
```

## Caching

```jsx
import { useResponseCache, useCache, withCache, revalidate, invalidate } from "@lazarv/react-server";

// Cache entire HTTP response for 30 seconds
useResponseCache(30_000);

// HOC form
export default withCache(async function Page() { ... }, 30_000);

// In-memory cache with compound key
const data = await useCache(
  ["posts", category],
  () => db.query("SELECT * FROM posts WHERE category = ?", [category]),
  60_000
);

// Invalidate
revalidate(["posts", category]);
```

Using the cache directive:
```jsx
"use cache; ttl=30000; tags=posts";

export async function getPosts() {
  return db.query("SELECT * FROM posts");
}

// Invalidate by tag
import { invalidate } from "@lazarv/react-server";
await invalidate(getPosts);
```

### Built-in cache providers

- `memory` — default in-memory cache
- `request` — per-request deduplication, shared across RSC and SSR (see below)
- `null` — no-op cache (useful with aliases to disable caching)
- `local` — browser localStorage
- `session` — browser sessionStorage

### Request-scoped caching

The `request` provider deduplicates calls within a single HTTP request. The function body runs once — all subsequent calls (including across RSC and SSR environments) return the cached result. Values are automatically dehydrated into the HTML and rehydrated in the browser during React hydration.

```jsx
async function getPageData() {
  "use cache: request";
  return await fetchExpensiveData();
}

// Server component — awaits the cached value
async function ServerPart() {
  const data = await getPageData();
  return <div>{data.title}</div>;
}

// Client component — uses React's use() with the cached value
"use client";
import { use } from "react";
function ClientPart() {
  const data = use(getPageData());
  return <div>{data.title}</div>;
}
```

Disable hydration (don't embed value in HTML) with either syntax:
```jsx
"use cache: request; hydrate=false";
// or equivalently:
"use cache: request; no-hydrate";
```

When `hydrate=false` / `no-hydrate` is set, SSR deduplication still works but the value is not embedded in the HTML — the client component recomputes it in the browser.

## Configuration

```js
// react-server.config.mjs
import { defineConfig } from "@lazarv/react-server/config";

export default defineConfig({
  root: "src/pages",              // File router root directory
  public: "public",               // Static assets directory
  port: 3000,                     // Server port
  adapter: "vercel",              // Deployment adapter
  // adapter: ["cloudflare", { serverlessFunctions: false }],
  scrollRestoration: true,        // Enable scroll restoration (or { behavior: "smooth" })
  mdx: {                          // MDX support
    remarkPlugins: [],
    rehypePlugins: [],
    components: "./src/mdx-components.jsx",
  },
  cache: {
    profiles: {
      short: { ttl: 60_000 },
    },
  },
  telemetry: {                    // OpenTelemetry
    enabled: true,
    serviceName: "my-app",
  },
});
```

JSON config with schema validation:
```json
{
  "$schema": "https://react-server.dev/schema.json",
  "port": 3000,
  "adapter": "vercel"
}
```

Extension configs are merged: `+tailwind.config.mjs`, `+auth.config.mjs`, etc.
Mode-specific: `.production.config.mjs`, `.development.config.mjs`, `.build.config.mjs`.
Env variables: `VITE_*` and `REACT_SERVER_*` prefixed vars are exposed via `import.meta.env`.

## Navigation

```jsx
import { Link, Refresh, ReactServerComponent, ScrollRestoration, useNavigate } from "@lazarv/react-server/navigation";

// Client-side navigation
<Link href="/about" prefetch>About</Link>

// Typed Link from route descriptor (compile-time param checking)
<user.Link params={{ id: 42 }}>User 42</user.Link>

// Link with search param updater
<products.Link search={(prev) => ({ ...prev, page: prev.page + 1 })}>Next</products.Link>

// Re-render current route
<Refresh>Reload</Refresh>

// Render an outlet
<ReactServerComponent outlet="sidebar">{sidebar}</ReactServerComponent>

// Scroll restoration
<ScrollRestoration behavior="smooth" />

// Programmatic navigation (client component only)
import { useClient } from "@lazarv/react-server/navigation";
const { navigate, replace, prefetch } = useClient();

// Typed programmatic navigation
const nav = useNavigate();
nav(user, { params: { id: 42 } });
```

## Error Handling

```jsx
import { ErrorBoundary } from "@lazarv/react-server/error-boundary";

<ErrorBoundary
  fallback={<p>Loading...</p>}
  component={({ error, resetErrorBoundary }) => (
    <div>
      <p>{error.message}</p>
      <button onClick={resetErrorBoundary}>Retry</button>
    </div>
  )}
>
  <Content />
</ErrorBoundary>
```

File router conventions: `error.page.jsx` for error boundaries, `loading.page.jsx` for Suspense fallbacks.

## Middleware

```js
// index.middleware.mjs
import { redirect, rewrite, usePathname, useMatch } from "@lazarv/react-server";

export default function Middleware() {
  if (useMatch("/old-path")) {
    redirect("/new-path");
  }
}
```

## API Routes

```js
// GET.posts.server.mjs
import { useSearchParams } from "@lazarv/react-server";

export default function GetPosts() {
  const params = useSearchParams();
  return Response.json({ posts: [], page: params.get("page") });
}
```

## Deployment Adapters

Available adapters: `vercel`, `netlify`, `cloudflare`, `aws`, `azure`, `azure-swa`, `bun`, `deno`, `docker`, `firebase`, `singlefile`.

```js
// react-server.config.mjs
export default { adapter: "vercel" };
// Or with options:
export default { adapter: ["cloudflare", { serverlessFunctions: false }] };
```

## Micro-Frontends

```jsx
import RemoteComponent from "@lazarv/react-server/remote";

export default function App() {
  return (
    <RemoteComponent
      src="http://localhost:3001"
      defer       // streaming
      isolate     // Shadow DOM isolation
      message="Hello"
    />
  );
}
```

## MCP (Model Context Protocol)

```js
// mcp.server.mjs
import { createServer, createTool } from "@lazarv/react-server/mcp";
import { z } from "zod";

export default createServer({
  tools: {
    search: createTool({
      id: "search",
      title: "Search",
      description: "Search the database",
      inputSchema: { query: z.string() },
      async handler({ query }) {
        return await db.search(query);
      },
    }),
  },
});
```

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.