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.
Scanned 6/5/2026
Install via CLI
openskills install lazarv/react-server---
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);
},
}),
},
});
```
No comments yet. Be the first to comment!