GuideEdit this page.md

Coming from Next.js

This page is for developers who already know the Next.js App Router, or who maintain older Pages Router apps, and want the mental model for @lazarv/react-server. It is not a migration checklist and it does not try to translate every file one by one. The goal is to explain which ideas stay familiar, which ones are intentionally different, and where a Next.js-specific feature maps to a @lazarv/react-server primitive or to userland code.

The Next.js side of this comparison targets the Next.js 16 App Router docs, especially the project structure, layouts and pages, Server and Client Components, fetching data, caching, revalidating, Route Handlers, Proxy, metadata, and deploying pages. For Pages Router context, it also references Pages Router, getServerSideProps, Pages Router SSR, and large page data.

Next.js is a full React framework with its own routing APIs, compiler integration, optimized components, cache model, metadata conventions, platform integrations, and next/* modules. @lazarv/react-server is a React Server Components runtime built on Vite. It gives you RSC rendering, streaming SSR, server functions, routing, caching, HTTP helpers, deployment adapters, and production runtime features, but it does not implement the next/* API surface.

If a dependency imports next/navigation, next/cache, next/server, next/image, next/font, next/script, or Next's Metadata API, treat that code as Next.js-specific. In @lazarv/react-server, you use React, Web Platform APIs, Vite plugins, and the runtime's own modules instead.

The most important shift is ownership. In Next.js, many behaviors are framework conventions. In @lazarv/react-server, the same category is usually a runtime primitive:

AreaNext.js App Router@lazarv/react-server
Framework shapeFull-stack React frameworkRSC runtime and server
Build systemNext compiler, Turbopack/Webpack integrationVite Environment API and Vite plugins
App structureapp directory by conventionAny router root; default src/pages, configurable to src/app
RoutingFile-system App RouterFile router plus code-based typed router
Type safetyRoute path typing and helper types in specific placesTyped route descriptors, typed links, typed params, typed search params, runtime validation
Data loadingServer Components, fetch, ORM/database calls, Cache ComponentsServer Components, typed resources, fetch, ORM/database calls, "use cache"
MutationsServer Functions / Server ActionsServer Functions with encrypted references and hardened decoding
MiddlewareProject-level proxy.ts in Next.js 16Segment-scoped *.middleware.* files and config handlers
APIsroute.ts Route Handlers*.server.* API routes and method-prefixed route files
DeploymentNode, Docker, static export, adapters, Vercel-optimized hostingNode, Bun, Deno, edge/serverless adapters, Docker, static export

The file-system router can be configured to look like a Next.js App Router project. This is useful when you like the src/app/page.tsx and src/app/layout.tsx shape, or when you want to move shared React components between projects.

react-server.config.json
{ "root": "src/app", "layout": { "include": ["**/layout.tsx"] }, "page": { "include": ["**/page.tsx"] } }

That configuration makes the route files familiar, but it does not make the app a Next.js app. Next.js modules and Next.js-only exports still do not become portable. The compatible part is the filesystem shape: pages, layouts, nested routes, dynamic segments, route groups, parallel route-like outlets, loading states, and error boundaries.

In a Next.js appIn @lazarv/react-serverWhat is essentially different
app/page.tsxpage.tsx, index.tsx, or any included page filePages are server components by default. File names are configurable, and named route files are allowed unless you restrict the include patterns.
app/layout.tsxlayout.tsxLayouts wrap child routes. @lazarv/react-server also generates typed layout helpers through @lazarv/react-server/routes.
Dynamic routes like [id]Dynamic files/directories like [id].page.tsx or [id]/page.tsxParams are passed as props and can be validated/coerced with schemas.
Catch-all routes[...slug] and [[...slug]]Catch-all params are arrays, and matcher aliases can decide whether a route matches before page code loads.
Route groups (group)Transparent route segments (group)Same URL idea, with typed route generation based on the final route shape.
Parallel routes @slotNamed outlets such as @sidebarOutlets are rendered as layout props and get branded types in generated route modules.
Intercepting routesNo direct Next.js intercepting-route conventionModel this with route structure, outlets, search params, local outlet navigation, or rewrites.
loading.tsxloading.page.tsx / loading route filesLoading state is tied to the route/layout boundary in the file router.
error.tsx / global errorserror.jsx, fallback.jsx, react-server.error.jsxError boundaries can be server or client components. Global server errors cannot be reset without a refresh.
not-found.tsx, forbidden.tsx, unauthorized.tsxFallback routes, error routes, middleware, status()There is no Next-specific special-file contract for these exact names in the documented model.
next/linkLink or typed route.Link from @lazarv/react-server/navigation / routesTyped links can merge search params as objects and support route/resource prefetching.
useRouter, usePathname, useSearchParamsuseClient, useNavigate, usePathname, useSearchParams, typed route hooksSearch params can be schema-validated, transformed, and updated functionally.
Server Components by defaultServer Components by defaultSame React model, but @lazarv/react-server owns the RSC runtime and pins React for protocol compatibility.
"use client" files"use client" files, inline client functions, client-only route pagesA page file with "use client" becomes a client-only route with no server round trip on navigation.
Server Actions / Server Functions"use server" functionsInline/module server functions work with forms, buttons, props, and client calls. References are encrypted by default.
fetch, database calls, ORM callsServer Components, typed resources, fetch, database calls, ORM callsResources add schema-validated keys, .use(), .query(), .prefetch(), .invalidate(), and route-resource bindings.
Cache Components and use cache"use cache" directive, useCache, withCache, useResponseCacheThe cache system is provider-based, tag/profile/TTL aware, and can also run with client storage providers.
revalidateTag, revalidatePath, updateTaginvalidate, revalidate, resource invalidationInvalidation targets runtime cache keys, cached functions, tags, resources, or response cache behavior.
ISR / static generation.static.* route files and export() configStatic paths are separate from the page component and can stream as async generators for large path sets.
Partial PrerenderingPPR with "use dynamic" and "use static"Dynamic components opt out of build-time rendering, while static components can preserve build-time values.
Pages Router getServerSideProps hydration data"use client" root plus "use cache: request"Both can SSR a client-owned tree with data available during hydration. Next serializes page props as JSON; @lazarv/react-server sends targeted RSC-serialized request-cache entries, not a full-page RSC payload.
route.ts Route Handlers*.server.* files, GET.*.server.mjs, method exportsUses standard Request/Response and runtime HTTP helpers.
proxy.ts (Middleware in older Next versions)*.middleware.* files and config handlersMiddleware can be segment-scoped instead of one project-level file.
cookies(), headers(), redirect()cookie, headers, redirect, rewrite, status, request/response hooksAvailable in server components, middleware, route handlers, and server functions through the runtime context.
Metadata APINormal React <head>, static files, route handlers, or app-level helpersNext's metadata and generateMetadata exports are not the API surface.
next/image, next/font, next/scriptBrowser/Vite/CSS/HTML primitives or third-party toolingThere are no Next optimized component equivalents built into the runtime.
CSS Modules, global CSS, Tailwind, Sass, CSS-in-JSVite CSS pipeline, Tailwind integration, CSS Modules, library integrationsFollow Vite and library setup. The runtime does not need Next-specific CSS handling.
next.config.tsreact-server.config.* plus vite.config.*Runtime settings live in react-server.config.*; bundler/plugin settings can stay in Vite config.
Environment variablesRuntime env plus Vite env conventionsThere is no NEXT_PUBLIC_ convention. Use Vite/public-env patterns or your own config boundary.
AuthUserland auth with route middleware/server functions/HTTP contextThe runtime gives request, cookie, header, CSRF, and server-function defenses, not an auth framework.
OpenTelemetry/instrumentationBuilt-in OpenTelemetry integration@lazarv/react-server instruments HTTP, middleware, RSC/SSR rendering, server functions, cache, startup, and Vite dev hooks when enabled.
DevtoolsNext dev overlay and framework tooling--devtools in @lazarv/react-server inspects RSC payloads, cache, routes, outlets, live components, workers, remotes, and logs.
Deploy to Vercel / Node / Docker / static / adaptersBuilt-in adapters for Vercel, Netlify, Cloudflare, AWS, Azure, Firebase, Bun, Deno, Docker, and single-file/static targetsDeployment is adapter-driven and not tied to one host. The production HTTP server is part of the runtime.
Pages Router APIsNo direct equivalentgetServerSideProps, getStaticProps, getStaticPaths, _app, and _document are Next Pages Router concepts. Use RSC, layouts, static files, and HTTP helpers instead.

Next.js presents a framework-level contract: you write into the app directory and import from next/*; Next decides how the compiler, router, cache, server, and host integration fit together.

@lazarv/react-server presents a runtime-level contract: you write React Server Components and run them with the @lazarv/react-server CLI. The runtime supplies the RSC encoder/decoder, HTML streaming, server function transport, HTTP context, file router, typed router, cache layer, and deployment adapters.

This changes a few assumptions:

If you are coming from the App Router, the basic route tree will feel familiar: folders define segments, page files create pages, layout files wrap children, dynamic params use brackets, route groups use parentheses, and @name directories describe parallel UI surfaces.

The main differences are type safety and configurability.

@lazarv/react-server can generate a virtual @lazarv/react-server/routes module for the file router. Each route descriptor can provide:

You can also skip the file router entirely and define a code-based router with createRoute and createRouter. That means a Next.js-style route tree is an option, not the only model.

NeedNext.js convention@lazarv/react-server convention
Index pageapp/page.tsxpage.tsx, index.tsx, or configured include
Nested pageapp/blog/page.tsxblog/page.tsx, blog.index.tsx, or configured include
Dynamic pageapp/blog/[slug]/page.tsxblog/[slug].page.tsx or blog/[slug]/page.tsx
Catch-allapp/docs/[...slug]/page.tsxdocs/[...slug].page.tsx
Optional catch-allapp/docs/[[...slug]]/page.tsxdocs/[[...slug]].page.tsx
Route groupapp/(marketing)/page.tsx(marketing)/page.tsx
Parallel UIapp/@modal/page.tsx@modal/page.tsx outlet
Loading UIloading.tsxloading.page.tsx / route loading file
Error UIerror.tsxerror.jsx / fallback.jsx
API routeroute.ts.server.* file or method-prefixed server file
Middleware/proxyproject-level proxy.tssegment-scoped .middleware.* files

For the App Router, the shared React rule still applies: components are server components by default, and "use client" marks the client boundary. Server components can fetch data and keep secrets on the server. Client components are for state, event handlers, effects, browser APIs, and client-only libraries.

@lazarv/react-server extends this model in two important ways:

  1. Inline directives can be lexically scoped. You can place "use client" or "use server" inside function bodies and let the compiler extract the correct module boundary.
  2. A file-router page that starts with "use client" becomes a client-only route. Navigation to that page happens in the browser without fetching a new RSC payload from the server, and state can be preserved between client-only route transitions.

There is also a Pages Router-like shape for apps that are intentionally client-rooted. If the root entry passed to @lazarv/react-server is a "use client" module, the runtime uses React DOM SSR directly for that root and skips the full page-level RSC Flight pipeline. Request-scoped "use cache: request" values can still hydrate through targeted RSC-serialized cache payloads. See Pages Router SSR and Hydration Data.

There are also additional runtime directives that do not exist in Next.js:

DirectivePurpose
"use hydrate"Render a server subtree as HTML now and hydrate it later as a local island.
"use live"Stream updates from an async generator component through live transports.
"use worker"Run exported async functions in server Worker Threads or browser Web Workers.
"use dynamic"Mark a component as request-time dynamic during partial pre-rendering.
"use static"Preserve build-time static values during partial pre-rendering.
"use cache"Cache functions/components through the runtime cache layer.

The closest Next.js Pages Router mental model is getServerSideProps: a page is rendered on the server for each request, data is returned as props, those props are serialized for the browser, and React hydrates the same page component with that data. Next's docs also warn that large Pages Router data can become costly because page data is serialized as __NEXT_DATA__ JSON and must be parsed before hydration.

@lazarv/react-server has a different version of this pattern for client-rooted apps. If your app entry is a "use client" module, the runtime detects that at startup and renders it with React DOM SSR directly:

src/index.jsx
"use client"; import App from "./App.jsx"; export default function Root() { return <App />; }

That path is normal React DOM SSR and hydration for the client root. It is not a full-page RSC payload. The runtime skips the page-level RSC Flight encode/decode because the root is already a client tree.

The important bridge is request-scoped cache hydration. A client component can read a request-cached function with use():

App.jsx
"use client"; import { use } from "react"; import { getRequestData } from "./get-request-data.mjs"; export default function App() { const data = use(getRequestData()); return <pre>{JSON.stringify(data, null, 2)}</pre>; }
get-request-data.mjs
let calls = 0; export async function getRequestData() { "use cache: request"; calls += 1; return { calls, renderedAt: new Date(), }; }

During SSR, getRequestData() runs once for the current HTTP request. The resolved value is stored in the request cache. As the HTML stream flushes, the runtime serializes hydration-eligible request-cache entries with the RSC serializer and injects them into self.__react_server_request_cache_entries__. During browser hydration, the client cache wrapper reads those values synchronously, so use(getRequestData()) resolves on the first render instead of suspending or recomputing a different value.

So the comparison is:

Pages Router concept@lazarv/react-server client-root SSR
getServerSideProps runs on each request"use cache: request" work runs once per request when read during SSR
Page props are serialized for hydrationHydration-eligible request-cache entries are serialized for hydration
Data is JSON page dataData is serialized through the RSC serializer, so React/RSC-supported values can survive the boundary
Hydration data is tied to the page props objectHydration data is tied to cache keys used by the client tree
Large __NEXT_DATA__ can delay hydration parsingOnly request-cache entries that need to hydrate are injected; use no-hydrate or hydrate=false for values that must stay server/request-local

This is useful for dashboard-style or Pages Router-style apps where the whole root is already a client tree and you still want SSR HTML plus consistent request-time data during hydration. For mostly server-rendered apps, the normal RSC root is still the better default because it ships less JavaScript and can keep server-only subtrees out of the browser entirely.

In the App Router, both systems encourage data fetching in Server Components. In Next.js, you commonly fetch with fetch, an ORM, a database client, or a server-only helper, then use Suspense and loading files to stream slow parts of the page.

In @lazarv/react-server, you can do the same plain async work inside Server Components:

products.page.tsx
export default async function ProductsPage() { const products = await db.products.findMany(); return <ProductList products={products} />; }

For larger apps, the runtime adds typed resources. A resource is a descriptor with a schema-validated key and a bound loader. It works on the server, on the client, or with a dual-loader pattern for SSR plus client-only navigation.

resources/products.ts
import { createResource } from "@lazarv/react-server/resources"; import { z } from "zod"; export const products = createResource({ key: z.object({ page: z.coerce.number().int().positive().default(1), }), }).bind(async ({ page }) => { "use cache; tags=products"; return db.products.page(page); });

Resources give you .use() for Suspense rendering, .query() for imperative code, .prefetch() for warming data before navigation, .invalidate() for updates, and route-resource bindings for parallel prefetching.

Next.js 16's current App Router cache model centers on Cache Components, the cacheComponents flag, "use cache", cacheLife, cacheTag, revalidateTag, updateTag, and revalidatePath.

@lazarv/react-server also uses "use cache", but the runtime model is different. The directive accepts inline options such as ttl, tags, and profile, and can target a cache provider:

products.ts
export async function getProducts() { "use cache; ttl=30000; tags=products"; return db.products.findMany(); }
settings.ts
export async function getSettings() { "use cache: file; profile=settings"; return readSettings(); }

The cache layer can use the default in-memory provider, Unstorage-backed providers, file storage, browser storage providers for client code, or a custom driver. You can also cache whole responses with withCache or useResponseCache, invalidate cached functions with invalidate, and revalidate compound keys with revalidate.

For rendering modes:

GoalNext.js concept@lazarv/react-server concept
Fully dynamic request-time renderingDynamic renderingDefault server rendering or "use dynamic" inside PPR
Static generationPrerendering, static routes, static export.static.* files and export() config
Incremental/cache revalidationCache Components and revalidation APIsCache TTL, tags, profiles, invalidate, revalidate
Partial prerenderingPPR / Cache Components modelPPR with Suspense, "use dynamic", and "use static"
Static shell plus interactive subtreeClient components, streaming, PPRHydration islands with "use hydrate"
Pages Router-style SSR plus hydration datagetServerSideProps props and __NEXT_DATA__"use client" root with React DOM SSR and targeted request-cache hydration payloads

Server Functions are the closest conceptual match to Next.js Server Actions. You mark an async function with "use server" and call it from a form, button, client component prop, or client-side event flow.

todo.page.tsx
import { invalidate, redirect } from "@lazarv/react-server"; async function getTodos() { "use cache; tags=todos"; return db.todos.findMany(); } async function createTodo(formData: FormData) { "use server"; await db.todos.create({ text: String(formData.get("text") ?? "") }); await invalidate(getTodos); redirect("/todos"); } export default function TodosPage() { return ( <form action={createTodo}> <input name="text" /> <button type="submit">Create</button> </form> ); }

The important @lazarv/react-server difference is the defense layer. Server function identifiers are encrypted by default, action POSTs can be protected by CSRF origin validation, and inbound RSC replies are decoded through configurable limits for payload size, depth, rows, strings, BigInts, streams, and hostile object shapes before application code runs.

Next.js App Router Route Handlers use route.ts files and Web Request / Response APIs. Next.js 16 also renamed Middleware to Proxy and uses a root-level proxy.ts file.

@lazarv/react-server uses standard Request / Response objects too, but the file conventions are different:

GET.posts.server.mjs
export default async function getPosts() { const posts = await db.posts.findMany(); return Response.json({ posts }); }
index.middleware.mjs
import { redirect, usePathname } from "@lazarv/react-server"; export default async function authMiddleware() { const pathname = usePathname(); if (pathname.startsWith("/admin") && !(await isSignedIn())) { redirect("/login"); } }

Middleware files are segment-scoped and compose with the route tree. API route handlers can be method-prefixed files such as GET.posts.server.mjs, or a .server.* file exporting method functions. The same HTTP context helpers work in Server Components, middleware, route handlers, and server functions:

Next.js App Router navigation is server-rendered by default. A route transition may need the next Server Component payload from the server, and Next uses prefetching, streaming, and client-side transitions to keep that fast.

@lazarv/react-server follows the same RSC navigation principle for server routes, but gives you more route-level typing:

products.page.tsx
import { products } from "@lazarv/react-server/routes"; export default products.createPage(() => { const search = products.useSearchParams(); return ( <products.Link search={(prev) => ({ ...prev, page: search.page + 1 })}> Next page </products.Link> ); });

Search params are not only strings passed through a router. They can be validated with Zod, ArkType, Valibot, or lightweight parse functions, and links can update them as objects or as functional updaters.

Client-only routes are the major navigation difference. If a page itself is a "use client" module, @lazarv/react-server can navigate to it without a server request and preserve state between those transitions.

Next.js has first-class optimized components and conventions for metadata, images, fonts, scripts, icons, Open Graph images, sitemap, robots, and other SEO files.

@lazarv/react-server does not provide Next's Metadata API or optimized next/image, next/font, and next/script components. Use the primitives that fit the job:

This is a deliberate boundary: the runtime focuses on React Server Components, HTTP, routing, caching, and deployment. Product-specific SEO and asset policy stay in application code or integrations.

Next.js centralizes framework configuration in next.config.*.

@lazarv/react-server splits concerns:

There is no NEXT_PUBLIC_ environment variable convention. Treat public environment variables as a bundler/application concern and use Vite-compatible patterns or explicit runtime config.

Neither runtime is an auth provider by itself. In Next.js, auth usually lives in middleware/proxy, Route Handlers, Server Actions, cookies, headers, and server-only data access helpers. In @lazarv/react-server, the equivalent places are middleware files, API routes, Server Functions, cookies, headers, and the HTTP context.

What @lazarv/react-server adds at the runtime boundary:

Use these as baseline defenses, then implement your actual session, authorization, tenant isolation, and data access policy in application code.

Next.js can deploy as a Node.js server, Docker container, static export, or through adapters, and it has especially deep integration with Vercel.

@lazarv/react-server uses the same CLI shape everywhere:

pnpm react-server build pnpm react-server start

Deployment behavior is selected by configuration and adapters. The repo ships adapters and docs for Vercel, Netlify, Cloudflare, AWS, Azure Functions, Azure Static Web Apps, Firebase Functions, Bun, Deno, Docker, and single-file/static modes.

Because the production HTTP layer is part of the runtime, self-hosted deployments can use built-in health checks, readiness checks, keep-alive/timeouts, graceful shutdown, adaptive backpressure, body limits, multipart limits, and OpenTelemetry instead of relying entirely on host-specific behavior.

Next.js has instrumentation files, OpenTelemetry support, framework spans, development overlay behavior, and official testing guides for tools such as Vitest, Jest, Playwright, and Cypress.

@lazarv/react-server keeps testing tool choice in userland, but the runtime itself has first-class inspection:

Some Next.js app topics do not have a direct runtime feature because they are usually better handled by libraries, platform config, or Vite plugins:

Topic@lazarv/react-server approach
InternationalizationUse route groups, params, middleware, typed routes, and your i18n library of choice.
MDXUse the file router's Markdown/MDX support with Remark/Rehype plugins and custom MDX components.
CSS-in-JSUse the library's SSR/Vite integration. For concrete setup examples, see the Mantine and MUI integration docs.
AnalyticsUse provider scripts/components or server-side event code.
CSP/security headersSet headers in middleware, route handlers, HTTP helpers, or host config.
PWAAdd manifest/service worker tooling through Vite or application code.
Multi-zone appsUse host routing, reverse proxies, remotes, or RemoteComponent for RSC micro-frontends.
Background jobsUse your job runner, queue, cron host, workers, or server functions depending on the task.
Real-time UIUse "use live" components, regular WebSockets/SSE, or your realtime service.
CPU-heavy workUse "use worker" for Node Worker Threads or browser Web Workers.

The places that most often surprise Next.js developers are:

  1. There is no next/* compatibility layer. You use @lazarv/react-server/*, React, Web APIs, and Vite.
  2. Routing can be both file-based and code-based, and the typed router is a first-class part of the model.
  3. Search params and route params can be validated and typed at the route boundary.
  4. Middleware is route-tree-aware rather than one root project file.
  5. Caching is not only fetch-or-framework cache behavior; it is a directive and provider system.
  6. A "use client" page is a client-only route, not just a client component under a server route.
  7. Hydration islands, live components, workers, remote components, and MCP endpoints are runtime features, not Next.js concepts.
  8. The production server has explicit operational controls that many frameworks leave to the host platform.

Stay close to Next.js when your app depends heavily on Next-specific optimized components, the Metadata API, existing next/* packages, Pages Router APIs, Vercel platform conventions, or a team workflow built around those abstractions.

Reach for @lazarv/react-server when you want a Vite-based RSC runtime, stronger typed routing/search params, an open deployment model, first-class HTTP runtime controls, route-scoped middleware, runtime-level server function defenses, hydration islands, live components, workers, or RSC-native micro-frontends.

For a broader feature matrix, see the comparison table. For architecture constraints and tradeoffs, read Architecture Tradeoffs.