⚠️ RebaseJS is under active development and not yet production-ready. APIs may change before v1.0. Feel free to explore, contribute, or star the repo.
Skip to content

Core Concepts

Understanding these five ideas covers 90% of how RebaseJS behaves.

1. Rendering Modes

RebaseJS has three primary rendering modes. Each is a deliberate choice per route — never a global setting.

ModeHow it worksWhen to use
CSR (default)Page renders in the browser. Server sends a minimal HTML shell.Dashboards, auth-gated apps, highly interactive local-first UIs.
SSRServer renders the full HTML on every request. Client hydrates.Public pages, SEO-critical content, content that changes per user.
PPR (Partial Prerendering)Static HTML shell pre-rendered at build time; dynamic sections stream in.Marketing pages with personalised widgets, landing pages.

Choosing a mode

Is this page public / needs SEO?
  Yes → SSR (export const ssr = true)
  No  → CSR (default, no config needed)

Does it have a static outer shell + dynamic inner content?
  Yes → PPR (export const ppr = true, export const ssr = true)

2. Route Kinds

The file name inside app/ determines what a file does. RebaseJS recognises five primary route kinds:

FileKindPurpose
page.tsxpageRenders a UI at the current path
layout.tsxlayoutWraps all child pages in a shared shell
loading.tsxloadingSuspense fallback shown while data loads
error.tsxerrorError boundary for the current subtree
route.tsapiHTTP endpoint (GET, POST, PUT, PATCH, DELETE)

The Rust scanner builds a route manifest at build time. Every route kind maps to a stable path pattern. The manifest is written to .rebasejs/dist/route-manifest.json and is the single source of truth for the whole system.


3. Server / Client Boundary

The boundary is enforced by file naming, not directives.

app/
  page.tsx              ← runs in the browser (CSR default)
  page.tsx + ssr=true   ← runs on the server AND the browser
  route.ts              ← runs on the server only (API handler)

lib/
  db.server.ts          ← runs on the server only
  utils.ts              ← shared (no DB imports allowed here)

Files ending in .server.ts are never bundled for the browser. The Rust compiler enforces this at build time — if a client module imports a .server.ts file directly, the build fails with a clear error.


4. Request Lifecycle

Browser request


Middleware (middleware.ts)
  │  auth checks, redirects, locale detection

Route handler
  ├── page.tsx (CSR) → serve HTML shell, browser fetches JS, renders
  ├── page.tsx (SSR) → render React to HTML on server, stream to browser
  └── route.ts       → run GET/POST/… handler, return Response


Server functions (*.server.ts)
  │  called by useServerData / useMutation / live components
  └── database, external APIs, secrets — never reach the browser

5. Build Pipeline

Running rebase build executes these steps in order:

1. Vite + Rolldown (Rust)
   └── Client bundle → .rebasejs/dist/client/

2. Rust route scanner
   └── Walks app/, classifies files, writes route-manifest.json

3. Route type generator
   └── Writes .rebasejs/routes.d.ts (RebaseRoutes union + typed navigate)

4. TypeScript type checker
   └── tsc --noEmit (routes.d.ts is available here)

5. Rust route reference checker
   └── Validates <RouteLink to>, <Link href>, navigate() against manifest

6. esbuild SSR bundle
   └── Server pages → .rebasejs/dist/server/

7. PPR shells (if any)
   └── Pre-renders static HTML → .rebasejs/ppr-cache/

8. Build ID
   └── git SHA (or FNV-1a hash fallback) → .rebasejs/dist/BUILD_ID

The key ordering constraint: route types are generated before tsc runs, so RebaseRoutes is always resolvable on the first build.


6. RebaseRoutes — Type-Safe Navigation

Every page route in app/ is reflected in a generated TypeScript union:

ts
// .rebasejs/routes.d.ts  (auto-generated — do not edit)
export type RebaseRoutes = "/" | "/about" | `/users/${string}` | `/posts/${string}`;

This type is used by navigate(), <RouteLink to>, and <Link href> so the compiler catches dead links at build time.

tsx
import { navigate } from "rebasejs/router";

// ✅ known route
navigate("/about");

// ✗ build error: "/abuot" is not assignable to RebaseRoutes
navigate("/abuot");

To enable these checks, add .rebasejs/routes.d.ts to your tsconfig.json:

json
{
  "include": ["app", ".rebasejs/routes.d.ts"]
}

Released under the MIT License.