Technical courage, not bureaucracy
What makes Nexus.js special is not only that it is fast or modern, but that it is an opinionated framework with courage. While other stacks have become giant bureaucracies (infinite cache layers and config), Nexus is born from technical purity that solves real problems in 2026.
Reactivity without the tax
Other frameworks punish you for interactivity: one animated button can mean tens of kilobytes of React or Vue runtime. With Runes ($state, $derived) and islands, JavaScript is surgical — only the code for that control ships. The rest is real HTML. SPA power with the weight of a 90s static site.
Pretext: security and speed together
Integrating pretext as a first-class entry means no “loading spinner hell”: data is ready when HTML arrives. Security is native — Nexus validates sessions and filters DB-shaped data before the browser even knows it exists, closing whole classes of accidental exposure bugs.
Hardened Mode: the framework as auditor
The compiler stops you from leaking API keys to the client or shipping known-vulnerable dependencies (CVEs). That is what separates toys from professional tools — Nexus helps you not ruin your week with a basic security mistake.
Why NexusFlow is the perfect showcase
NexusFlow proves all three at once: the editor stays fluid with islands, auth leans on Hardened Mode, and the dashboard loads in milliseconds thanks to pretext.
The invisible effect
The best part of Nexus is that you do not feel you are fighting the framework — no endless useEffect, memo wars, or webpack archaeology. You write HTML, CSS, and standard JavaScript; the compiler does the dirty work. Nexus is what developers would build for themselves if they had the time to do it right from scratch.
What excites you more: island speed or the peace of mind from Hardened Mode?
Built for multi-tenant products
Nexus treats tenant subdomains and custom domains as first-class routing concerns — not middleware glue. Ideal for B2B SaaS, agencies with white-label portals, and internal tools per company.
Subdomain per tenant
Serve acme.yourplatform.com, globex.yourplatform.com, and staging--client.app.com from one deployment. The router resolves the tenant from Host before your handlers run.
Custom domains
Map customer-owned DNS (CNAME) to your edge. tenant.isCustomDomain distinguishes branded URLs from shared subdomains for analytics and billing.
Isolation by default
Cache keys are prefixed per tenant, responses can carry Vary: Host, and the DB adapter warns when queries omit tenantId — reducing cross-tenant mistakes in production.
Typed tenant info
Use extractTenant() from @nexus_js/router and attach the result to your context (e.g. ctx.locals.tenant). Resolve branding and plan data in resolve() once; read tenant.meta in server blocks.
*.app.com → your load balancer → Nexus with tenant.mode: 'subdomain'. For enterprise customers, add a row in your DB and point their domain with a CNAME to your edge — same codebase, isolated context.
Installation
Nexus requires Node.js ≥ 22 and pnpm ≥ 9. It works on any OS.
Prerequisites
node --version # Must be ≥ 20.0.0
npm install -g pnpm
pnpm --version # Must be ≥ 9.0.0
Quick Start
Create a new Nexus project in seconds with the official scaffolder.
Scaffold a new project
pnpm create nexus@latest my-app
cd my-app
Install dependencies
pnpm install
Start the dev server
pnpm dev
# → http://localhost:3000
"*.nx": "html" to your .vscode/settings.json → files.associations. Nexus already includes this in every scaffolded project.
Project Structure
A typical Nexus app looks like this:
my-app/
├── src/
│ ├── routes/
│ │ ├── +layout.nx # Root layout (wraps every page)
│ │ ├── +page.nx # → /
│ │ ├── blog/
│ │ │ ├── +layout.nx # Blog layout
│ │ │ ├── +page.nx # → /blog
│ │ │ └── [slug]/
│ │ │ ├── +page.nx # → /blog/:slug
│ │ │ ├── +server.nx # API: GET/POST /blog/:slug
│ │ │ └── error.nx # Error boundary for this route
│ │ └── (auth)/ # Route group (no URL segment)
│ │ ├── login/+page.nx
│ │ └── signup/+page.nx
│ └── components/
│ ├── Header.nx
│ └── Counter.nx
├── public/ # Static files served as-is
├── nexus.config.ts # Framework configuration
├── tsconfig.json
└── package.json
The .nx Component Format
Every Nexus page and component is a .nx file with up to four sections.
---
// ① SERVER BLOCK — runs ONLY on the server, never sent to browser
// Fetch data, access env vars, call the DB
import { cache } from '@nexus_js/runtime';
import { defineHead } from '@nexus_js/head';
const posts = await cache(() => fetch('/api/posts').then(r => r.json()), {
ttl: 60, // Cache for 60 seconds
tags: ['post'], // Invalidate with revalidate(['post'])
});
defineHead({
title: 'My Blog — Nexus',
description: 'The best articles on the web',
og: { image: '/og.png' },
});
---
<script>
// ② ISLAND BLOCK — reactive code, runs in the browser
// Uses Svelte 5 Rune syntax for fine-grained reactivity
let { initialCount = 0 } = $props();
let count = $state(initialCount);
let doubled = $derived(count * 2);
$effect(() => {
console.log('Count is now:', count);
});
</script>
<template>
<!-- ③ TEMPLATE — HTML first, zero JS unless islands are used -->
<!-- Static content (zero JS cost) -->
<ul>
{#each posts as post}
<li><a href="/blog/{post.slug}">{post.title}</a></li>
{/each}
</ul>
<!-- Interactive island — hydrated when visible in viewport -->
<Counter client:visible initialCount={42} />
<!-- Server-only component (never sends JS to browser) -->
<HeavyChart server:only data={chartData} />
</template>
<style>
/* ④ STYLE — automatically scoped to this component */
/* No class name collisions, ever */
li { padding: 8px 0; border-bottom: 1px solid #eee; }
a { color: var(--accent); text-decoration: none; }
</style>
Section Rules
| Section | Delimiters | Runs | Access to |
|---|---|---|---|
| --- Server | --- ... --- | Server only | DB, env vars, cookies, file system |
| <script> Island | <script> ... </script> | Browser only | DOM, Runes, user events |
| <template> | <template> ... </template> | Both (SSR + hydrate) | All data from server + island |
| <style> | <style> ... </style> | Build-time (AOT) | N/A — compiled to scoped CSS |
File-Based Routing
The file system is your router. No config required.
| File | URL | Description |
|---|---|---|
+page.nx | / | Home page |
blog/+page.nx | /blog | Static route |
blog/[slug]/+page.nx | /blog/:slug | Dynamic segment |
blog/[...rest]/+page.nx | /blog/* | Catch-all |
(auth)/login/+page.nx | /login | Route group (no segment) |
+layout.nx | all children | Shared layout wrapper |
+server.nx | same route | API endpoint (GET/POST/etc.) |
error.nx | same dir | Error boundary |
Dynamic Routes
---
// ctx.params is fully typed from your route pattern
const { slug } = ctx.params;
const post = await ctx.db.query('post', 'findOne', () =>
ctx.db.client.post.findUnique({ where: { slug } })
);
if (!post) {
ctx.status(404);
ctx.redirect('/blog');
}
---
<template>
<article>
<h1>{post.title}</h1>
<p>{post.body}</p>
</article>
</template>
Layouts
Layouts wrap child pages with shared UI. They compose from outermost to innermost.
<template>
<header>
<nav>
<a href="/">Home</a>
<a href="/blog">Blog</a>
</nav>
</header>
<main>
<!-- Child page renders here -->
<!--nexus:slot-->
</main>
<footer>© 2026 My App</footer>
</template>
API Routes (+server.nx)
---
// Export HTTP method handlers directly
export async function GET(ctx) {
const posts = await ctx.db.client.post.findMany();
return ctx.json(posts);
}
export async function POST(ctx) {
const body = await ctx.request.json();
const post = await ctx.db.client.post.create({ data: body });
return ctx.json(post, 201);
}
---
Islands Architecture
Every component is static by default. Add a hydration directive only where interactivity is needed.
| Directive | When it hydrates | Best for |
|---|---|---|
client:load | Immediately on page load | Critical UI — modals, nav, auth forms |
client:idle | After requestIdleCallback | Non-critical widgets, analytics |
client:visible | When entering the viewport (IntersectionObserver) | Below-the-fold content, carousels |
client:media="(max-width:768px)" | When media query matches | Mobile-only components |
server:only | Never — pure SSR | Heavy charts, admin tables, no JS |
<template>
<!-- Hero text: zero JS. Pure HTML. Fastest possible. -->
<h1>Welcome</h1>
<!-- Nav dropdown: needs JS immediately -->
<NavDropdown client:load />
<!-- Comments section: loads when user scrolls to it -->
<Comments client:visible postId={post.id} />
<!-- Mobile menu: only costs JS on mobile -->
<MobileMenu client:media="(max-width: 768px)" />
<!-- Data table: 80kb of Ag-Grid, never sent to browser -->
<AdminTable server:only rows={rows} />
</template>
<link rel="modulepreload"> for all nested islands in parallel — before the browser discovers them itself.
Runes — Reactive Primitives
Nexus implements Svelte 5 Rune semantics. The most ergonomic reactivity system in the JavaScript ecosystem.
$state — Reactive Value
let count = $state(0);
let user = $state<User | null>(null);
let items = $state<string[]>([]);
// Mutate directly — no .value, no setState()
count++;
items.push('new item');
user = { name: 'Alice', role: 'admin' };
$derived — Computed Value
let count = $state(0);
let doubled = $derived(count * 2);
let label = $derived(count === 0 ? 'empty' : `${count} items`);
let heavy = $derived.by(() => {
// For multi-line derivations
return items.filter(i => i.active).length;
});
$effect — Side Effects
$effect(() => {
// Runs after render, re-runs when count changes
document.title = `Count: ${count}`;
});
$effect(() => {
const id = setInterval(() => count++, 1000);
// Return a cleanup function
return () => clearInterval(id);
});
$props — Component Props
// In a component's <script> block
let {
title,
count = 0, // default value
onSave, // callback
class: className, // rename reserved words
} = $props();
$optimistic — Optimistic UI
// Instant UI update before server confirms
const savePost = $optimistic(
async (draft: Post) => {
return await callAction('savePost', draft);
},
// Optimistic updater — applied instantly
(current: Post[], draft: Post) => {
return current.map(p => p.id === draft.id ? { ...p, ...draft } : p);
}
);
// Usage:
await savePost(editedPost);
// UI updates immediately, rolls back if server errors
$sync — Synchronized State
// Stays in sync with cookies/sessionStorage/localStorage
const theme = $sync('theme', {
default: 'light' as 'light' | 'dark',
storage: 'cookie', // or 'session' | 'local'
path: '/',
});
// Changing theme updates the cookie automatically
theme.value = 'dark';
// Refreshing the page restores the value automatically
Server Actions
Type-safe functions that run on the server, called from the client like regular async functions. Race-condition-safe by design.
Defining a Server Action
---
import { db } from '$lib/server/db';
// Mark with "use server" — the compiler extracts this to a server module
async function createPost(formData: FormData, ctx: NexusContext) {
"use server";
const title = formData.get('title') as string;
const body = formData.get('body') as string;
const post = await db.mutate('post', 'create', () =>
db.client.post.create({ data: { title, body } })
);
// Invalidates the 'post' cache tag everywhere
await revalidate(['post']);
return post; // Date fields serialize automatically ✓
}
---
<script>
let title = $state('');
let body = $state('');
let saving = $state(false);
let error = $state<string | null>(null);
async function handleSubmit(e: SubmitEvent) {
e.preventDefault();
saving = true;
error = null;
try {
const result = await callAction('createPost', new FormData(e.target as HTMLFormElement));
console.log('Created:', result);
} catch (err) {
error = err.message;
} finally {
saving = false;
}
}
</script>
<template>
<form onsubmit={handleSubmit}>
<input name="title" bind:value={title} placeholder="Title" />
<textarea name="body" bind:value={body}></textarea>
<button type="submit" disabled={saving}>
{saving ? 'Saving…' : 'Publish'}
</button>
{#if error}<p class="error">{error}</p>{/if}
</form>
</template>
Race Condition Strategies
Configure how concurrent calls to the same action are handled:
import { registerAction } from '@nexus_js/server/actions';
// 'cancel' — abort previous, run latest (search, autosave)
registerAction('search', searchFn, { race: 'cancel' });
// 'queue' — run sequentially in order (cart updates)
registerAction('addToCart', cartFn, { race: 'queue' });
// 'reject' — 409 if one is already in flight (payments)
registerAction('checkout', payFn, { race: 'reject' });
// 'ignore' — all run in parallel (analytics, logging)
registerAction('trackEvent', trackFn, { race: 'ignore' });
// Idempotent — client sends x-nexus-idempotency; same key returns cached result
registerAction('importData', importFn, { idempotent: true, timeout: 120_000 });
Secure payments & plan checks
Nexus does not include Stripe or a billing engine. For checkout-style actions use race: 'reject' (blocks concurrent double-submit → 409), Zod schema for amounts and SKUs, rateLimit, and optional idempotent: true with the x-nexus-idempotency header for safe retries. For subscription / plan rules, load entitlements in TenantConfig.resolve or auth middleware into tenant.meta or ctx.locals and enforce them inside the action (403 if the plan disallows the feature). See PRODUCTION.md → Payments & plan limits.
Cache & Revalidation
Stale-while-revalidate caching with tag-based invalidation. Smart Cache-Control headers computed automatically.
---
import { cache, revalidate } from '@nexus_js/runtime';
// Cache for 60 seconds, tag as 'posts'
const posts = await cache(() => db.post.findMany(), {
ttl: 60,
tags: ['post', 'homepage'],
});
// Immutable — cache forever (static assets/data)
const config = await cache(() => readConfig(), { ttl: Infinity });
// No cache (sensitive/personalized data)
const cart = await cache(() => getCart(userId), { ttl: 0 });
---
async function deletePost(id: string, ctx: NexusContext) {
"use server";
await ctx.db.client.post.delete({ where: { id } });
// Purge all routes that fetched with tag 'post'
await revalidate(['post']);
// Or purge a specific path
await revalidatePath('/blog');
}
ttl values used during rendering and emits the most conservative Cache-Control header. A page with ttl: 60 automatically gets s-maxage=60, stale-while-revalidate=120. No manual header configuration needed.
Database — Bring Your Own
Nexus doesn't bundle an ORM. It wraps your client with caching, invalidation, and Edge awareness.
Setup (shared DB module)
NexusConfig in @nexus_js/cli does not yet wire db into NexusContext automatically. Create a provider with @nexus_js/db and import it from your server blocks and actions (or attach it to ctx.locals in your own middleware).
import { defineDB } from '@nexus_js/db';
import { PrismaClient } from '@prisma/client';
export const db = defineDB(new PrismaClient(), {
defaultTtl: 60,
tags: (table) => [table],
});
Prisma Adapter
import { prismaAdapter } from '@nexus_js/db/adapters/prisma';
import { PrismaClient } from '@prisma/client';
const db = prismaAdapter(new PrismaClient(), {
slowQueryThreshold: 200, // Warn if query > 200ms
});
Drizzle Adapter (Edge-native)
import { drizzleAdapter } from '@nexus_js/db/adapters/drizzle';
import { drizzle } from 'drizzle-orm/neon-http';
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL!);
const db = drizzleAdapter(drizzle(sql), { edge: true });
Turso / libSQL Adapter
import { libsqlAdapter } from '@nexus_js/db/adapters/libsql';
import { createClient } from '@libsql/client';
const db = libsqlAdapter(createClient({
url: process.env.TURSO_URL!,
authToken: process.env.TURSO_TOKEN,
}));
Using the DB in Server Actions
// In any .nx server block or Server Action — import your provider
import { db } from '$lib/server/db';
// Cached query — result stored for defaultTtl when > 0
const posts = await db.query('post', 'findMany', () =>
db.client.post.findMany({ where: { published: true } })
);
// Mutation — auto-invalidates table tags via revalidate()
const post = await db.mutate('post', 'create', () =>
db.client.post.create({ data: { title, body } })
);
Streaming SSR
Send the HTML shell to the browser instantly, then stream in the slow parts as they resolve — without blocking the fast parts.
---
import { Suspense } from '@nexus_js/server/streaming';
// Fast data: available immediately
const user = await getUser(ctx);
// Slow data: wrapped in Suspense — doesn't block page render
const slowData = Suspense(
fetchAnalytics(), // Promise
{
fallback: '<div class="skeleton">Loading…</div>',
render: (data) => `<Chart data="${JSON.stringify(data)}" />`,
}
);
---
<template>
<!-- Renders immediately, user doesn't wait -->
<h1>Welcome, {user.name}</h1>
<!-- Placeholder shown while slowData resolves -->
{slowData.placeholder}
</template>
<template> placeholder for each Suspense boundary. When the Promise resolves, the server sends a tiny script that teleports the resolved HTML into position — with zero client-side fetching.
Global State Store
Shared state across islands and pages. Hydration Miss = 0. State is never lost during SPA navigation.
<script>
import { useStore } from '@nexus_js/runtime';
// State persists in sessionStorage across /shop → /checkout → /confirm
const cart = useStore('cart', {
default: [] as CartItem[],
persist: 'session', // 'memory' | 'session' | 'url'
});
function addItem(item: CartItem) {
cart.value = [...cart.value, item];
}
function removeItem(id: string) {
cart.value = cart.value.filter(i => i.id !== id);
}
</script>
<template>
<div class="cart">
{#each cart.value as item}
<div>{item.name} <button onclick={() => removeItem(item.id)}>×</button></div>
{/each}
<p>Total: {cart.value.length} items</p>
</div>
</template>
Any other island can read the same store by using the same key:
// In any island anywhere in the app
const cart = useStore('cart', { default: [] });
console.log(cart.value); // [{ id: 'sku-1', name: 'Widget', qty: 2 }]
Persistence Modes
| Mode | Survives | Use for |
|---|---|---|
'memory' | Navigation (SPA) | Ephemeral UI state |
'session' | Hard refresh (same tab) | Cart, form drafts, wizard state |
'url' | Sharing the URL | Filters, search, pagination |
Middleware
Web-standard middleware pipeline. Runs on Node.js, Cloudflare Workers, and Vercel Edge.
import { sequence, cors, rateLimit, auth, securityHeaders } from '@nexus_js/middleware';
export const middleware = sequence(
securityHeaders(), // CSP, HSTS, X-Frame-Options
cors({ origins: ['https://myapp.com'] }),
rateLimit({ max: 100, window: 60 }), // 100 req/min per IP
auth({
// Protect routes matching /admin/*
protect: /^\/admin/,
redirectTo: '/login',
verify: async (req) => {
const token = req.headers.get('authorization')?.slice(7);
return token ? await verifyJWT(token) : null;
},
}),
);
Custom Middleware
import type { MiddlewareFn } from '@nexus_js/middleware';
export const requestLogger: MiddlewareFn = async (request, next) => {
const start = Date.now();
const response = await next(request);
console.log(`${request.method} ${request.url} — ${Date.now() - start}ms`);
return response;
};
Edge Adapters
// Cloudflare Workers
import { toCloudflareHandler } from '@nexus_js/middleware';
export default toCloudflareHandler(middleware);
// Vercel Edge Functions
import { toVercelEdge } from '@nexus_js/middleware';
export default toVercelEdge(middleware);
SEO & Head Management
Server-side metadata injection with client-side reactive updates.
---
import { defineHead } from '@nexus_js/head';
const post = await ctx.db.query('post', 'findOne', () =>
ctx.db.client.post.findUnique({ where: { slug: ctx.params.slug } })
);
// Sets <title>, <meta>, Open Graph, Twitter Card — all server-side
defineHead({
title: `${post.title} — My Blog`,
description: post.excerpt,
canonical: `https://myblog.com/blog/${post.slug}`,
og: {
title: post.title,
description: post.excerpt,
image: post.coverImage,
type: 'article',
},
twitter: {
card: 'summary_large_image',
creator: '@myhandle',
},
// Inject arbitrary tags
meta: [
{ name: 'author', content: post.author.name },
],
link: [
{ rel: 'canonical', href: `https://myblog.com/blog/${post.slug}` },
],
});
---
Reactive Head Updates (Client-side)
import { useHead } from '@nexus_js/head';
// In an island — updates <title> reactively
let count = $state(0);
useHead(() => ({
title: count === 0 ? 'Inbox' : `Inbox (${count})`,
}));
Assets — Images & Fonts
Automatic AVIF/WebP conversion, responsive srcsets, blur placeholders, and font optimization.
Images
---
import { renderImage } from '@nexus_js/assets';
// Generates AVIF/WebP srcset, blur placeholder, lazy loading
const heroImg = renderImage('/hero.jpg', {
width: 1200,
alt: 'Nexus Framework hero',
priority: true, // preload (LCP image)
quality: 85,
});
---
<template>
{heroImg}
<!-- Outputs:
<picture>
<source srcset="/hero.avif 1200w" type="image/avif">
<source srcset="/hero.webp 1200w" type="image/webp">
<img src="/hero.jpg" width="1200" loading="eager" ...>
</picture>
-->
</template>
Fonts
Use regular <link> tags in your root +layout.nx (see the default scaffold from create-nexus). Image formats and responsive sizes are configured on NexusConfig via images — not a separate assets.fonts block.
<head>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" rel="stylesheet">
</head>
CLI Reference
All Nexus commands follow the same pattern: nexus <command> [options]
| Command | Description | Since |
|---|---|---|
nexus dev | Start dev server with HMR on localhost:3000 + background CVE audit | v0.1 |
nexus dev --port 4200 | Dev server on custom port | v0.1 |
nexus build | Production build → .nexus/output/, .nexus/build-id.json, .nexus/lib/ from src/lib (fails on critical CVEs in block mode) | v0.1+ |
nexus start | Start the production server | v0.1 |
nexus studio | Open Nexus Studio at localhost:4000 | v0.2 |
nexus routes | Print the full route manifest | v0.1 |
nexus check | TypeScript type-check (runs tsc --noEmit) | v0.1 |
nexus analyze | Bundle budget report per route | v0.1 |
nexus add <block> | Install a Nexus Block from the marketplace | v0.3 |
nexus audit | Run full security audit (code + deps) | v0.5 |
nexus audit --ci | Exit 1 on any critical finding — designed for CI pipelines | v0.5 |
nexus audit --json | Machine-readable JSON output | v0.5 |
nexus fix | Auto-update vulnerable dependencies to patched versions | v0.6 |
nexus fix --dry-run | Preview fixes without writing any files | v0.6 |
nexus fix --force | Also fix medium + low severity vulnerabilities | v0.6 |
create-nexus my-app | Scaffold a new Nexus project | v0.1 |
nexus.config.ts
Scaffolded apps export a plain object typed with satisfies NexusConfig (see create-nexus). The shape below matches what @nexus_js/cli actually loads today — extra keys are ignored unless the runtime reads them.
import type { NexusConfig } from '@nexus_js/cli';
export default {
defaultHydration: 'client:visible',
images: {
formats: ['avif', 'webp'],
sizes: [640, 1280, 1920],
quality: 85,
},
server: {
port: 3000,
host: 'localhost',
streamingPretext: false,
},
build: {
outDir: '.nexus/output',
sourcemap: true,
adapter: 'node',
},
i18n: {
defaultLocale: 'en',
locales: ['en', 'es', 'pt'],
translationsDir: './src/locales',
},
plugins: [
{ name: 'my-transform', transform(src, path) { return src; } },
],
browser: {
importMap: { /* optional: island import map entries */ },
},
security: {
hardened: true,
failOnIslandSecurity: true,
shieldLite: false,
audit: { failOn: ['critical', 'high'], blockBuild: true },
allowVulnerable: {
'some-pdf-lib': {
cve: 'CVE-2025-1234',
reason: 'Build-time only, never ships to browser.',
expires: '2026-06-01',
},
},
},
observability: {
enabled: true,
traceActions: true,
},
} satisfies NexusConfig;
vite.config.ts (Vite integration)
For Vite, use defineNexusConfig from vite-plugin-nexus — that helper merges the Nexus plugin into a normal Vite config (it is not the same file as nexus.config.ts).
import { defineConfig } from 'vite';
import { defineNexusConfig } from 'vite-plugin-nexus';
export default defineConfig(defineNexusConfig({
// standard Vite options + Nexus plugin
}));
Nexus Studio
A real-time developer dashboard with zero external dependencies.
nexus studio
# → Opens http://localhost:4000
Layout Tree
Visual hierarchy of nested layouts for the current route.
Island Map
All live islands, their reactive state, and hydration strategy.
Action Log
Real-time stream of Server Action calls with payloads and timings.
Cache Inspector
Active cache entries, TTLs, hit/miss ratio.
Store Viewer
Live snapshot of the Global State Store across islands.
JS Cost
Bundle budget for the current route (raw + gzip).
Security Panel
Live view of CSRF tokens, rate-limit windows, and secret leak scan results.
Sync Queue
Pending $localSync operations, conflict log, and online/offline state.
Testing
First-class Vitest integration with helpers for SSR, islands, and Server Actions.
SSR Testing
import { renderSSR } from '@nexus_js/testing';
test('renders blog post', async () => {
const { html, status } = await renderSSR('/blog/hello-world');
expect(status).toBe(200);
expect(html).toContain('Hello World');
expect(html).toContain('<title>Hello World — My Blog</title>');
});
Island Testing
import { mountIsland, fireEvent } from '@nexus_js/testing';
test('counter increments', async () => {
const { getByText, getByRole } = await mountIsland('./Counter.nx', {
props: { initialCount: 5 },
});
expect(getByText('Count: 5')).toBeTruthy();
await fireEvent.click(getByRole('button', { name: 'Increment' }));
expect(getByText('Count: 6')).toBeTruthy();
});
Server Action Testing
import { createActionTestHarness } from '@nexus_js/testing';
test('createPost saves to DB', async () => {
const harness = createActionTestHarness('./src/routes/+page.nx');
const actions = await harness.listActions();
expect(actions).toContain('createPost');
const formData = new FormData();
formData.set('title', 'Test Post');
formData.set('body', 'Hello!');
const result = await harness.invokeAction('createPost', formData);
expect(result.title).toBe('Test Post');
});
Deployment
Nexus targets the Web Platform. Any runtime that speaks Request/Response works.
Node.js (VPS / Docker)
nexus build
nexus start # Starts on PORT env var or 3000
# Production: set NEXUS_SECRET; optional NEXUS_BUILD_ID at build time (see Production hardening)
# Docker
docker build -t my-app .
docker run -p 3000:3000 my-app
Cloudflare Workers
// src/entry.cloudflare.ts
import { toCloudflareHandler } from '@nexus_js/middleware';
import { createNexusServer } from '@nexus_js/server';
const server = await createNexusServer({ root: '.', dev: false });
export default toCloudflareHandler(server.fetch);
Vercel
// vercel.json
{
"builds": [{ "src": "package.json", "use": "@vercel/node" }],
"routes": [{ "src": "/(.*)", "dest": "/" }]
}
Deno Deploy / Bun
// Nexus uses only Web-standard APIs
// No Node.js-specific APIs in the critical path
// Works on Deno Deploy and Bun without modification
📦 Production hardening
Build artifacts, build-ID consistency for server actions, and environment variables that matter when NODE_ENV=production.
- Artifacts —
nexus buildwrites.nexus/output/,.nexus/build-id.json(buildId+generatedAt), and transpiledsrc/lib→.nexus/lib/**/*.jsfor runtime$libresolution. - Stale tabs after deploy — When a build id is present, the server expects
x-nexus-build-idon action requests; mismatch → 412BUILD_MISMATCH. HTML injectswindow.__NEXUS_BUILD_ID__;callActionsends the header and reloads on 412. - CI — Set
NEXUS_BUILD_IDduringnexus build(e.g. git SHA) so deployed HTML and server share the same id. - Secrets & errors — Set
NEXUS_SECRETin production. Unhandled action errors are masked unlessNEXUS_EXPOSE_ERRORS=true(responses includeerrorId; details in server logs).
Full reference (CSRF tiers, rate limits, body limits, URL helpers): nexusjs.dev/docs/PRODUCTION.md ↗ · Deploy repo: nexusjs-site ↗ · Monorepo copy: nexus/docs ↗
🏢 Multi-Tenant — configuration & API
This section complements Enterprise & subdomains: here is the concrete nexus.config.ts surface and how tenant flows into routes.
Routing modes
tenant.mode | Use case | Example |
|---|---|---|
'subdomain' | Wildcard SaaS — one app, many tenants on *.baseDomain | acme.app.com → tenant acme |
'custom-domain' | Bring-your-own-domain — customer CNAMEs to your edge | portal.customer.com → resolved tenant row |
'path' | Single host, tenant in URL (admin consoles, docs) | app.com/t/acme/dashboard |
How it works
The router inspects Host (and path when using 'path' mode), resolves a stable tenantId, and merges resolveMeta() into ctx.tenant before your .nx server block runs. Cache tags, server cache keys, and optional DB helpers can all key off that id — so subdomains for companies are not a second-class concern layered on top of routing.
DNS checklist (subdomain SaaS)
- Wildcard record —
*.app.comA/AAAA or CNAME to your edge (Vercel, Cloudflare, Fly, etc.). - APEX —
app.comfor marketing or login; tenant app lives on subdomains or custom hosts. - TLS — issue a wildcard cert for
*.app.com; per-customer certs when using custom domains (often via your provider’s automation). - Staging — separate
*.staging.app.comor path-based tenants to avoid polluting production metadata.
Tenant detection lives in @nexus_js/router (extractTenant). Call it from your server bootstrap or middleware and attach the result to ctx.locals (or your own context type) — it is not yet a top-level field on NexusConfig.
import { extractTenant } from '@nexus_js/router';
import type { TenantConfig } from '@nexus_js/router';
const tenantConfig: TenantConfig = {
mode: 'subdomain', // 'subdomain' | 'custom-domain' | 'path' | 'disabled'
baseDomain: 'app.com',
async resolve(tenantId, _req) {
return (await db.tenants.findUnique({ where: { id: tenantId } })) ?? null;
},
};
// const tenant = await extractTenant(request, tenantConfig);
// then merge into your handler context (e.g. ctx.locals.tenant = tenant)
Route files
---
// After you attach extractTenant() to context (e.g. ctx.locals.tenant)
const tenant = ctx.locals.tenant as { id: string };
if (!tenant) return ctx.redirect('/', 302);
const data = await db.products.findMany({
where: { tenantId: tenant.id }, // always scope by tenant in your queries
});
---
<template>
<h1>Welcome, {tenant.meta.name}!</h1>
{#each data as item}
<ProductCard {item} />
{/each}
</template>
Automatic isolation
| Concern | Nexus Behavior |
|---|---|
| Cache keys | Prefixed with tenant:{id}: — zero cross-tenant cache bleed |
| Cookies | Scoped per tenant domain / subdomain by the browser |
| DB queries | @nexus_js/db adapter warns if tenantId filter is missing |
| Vary headers | Automatic Vary: Host on tenant-aware responses where applicable |
| Server Actions | CSRF tiers bind requests to the app origin; combine with per-tenant scoping in your own auth layer |
📴 Local-First Sync Engine
Inspired by Replicache and ElectricSQL — writes are instant (IndexedDB latency: 0ms) and sync to the server in the background when online.
The $localSync rune
<script>
import { $localSync } from '@nexus_js/sync';
// Reactive, offline-first collection — backed by IndexedDB
const captures = $localSync('pokemon-captures', {
endpoint: '/_nexus/sync', // server sync endpoint
conflict: 'last-write-wins', // or (local, remote) => resolved
});
async function capture(pokemon) {
// Writes locally in <1ms, queues for server sync
await captures.push({ id: pokemon.id, name: pokemon.name, ts: Date.now() });
}
async function remove(id) {
await captures.remove(id);
}
</script>
<template>
<p>Status: {captures.status}</p> <!-- 'synced' | 'pending' | 'error' -->
<p>Pending ops: {captures.pending}</p>
{#each captures.value as c}
<div>{c.name} <button on:click={() => remove(c.id)}>Release</button></div>
{/each}
<button on:click={() => captures.flush()}>Sync now</button>
</template>
Server sync endpoint
export async function POST({ request }) {
const { ops } = await request.json();
const results = [];
for (const op of ops) {
if (op.type === 'push') {
await db.captures.upsert({ where: { id: op.key }, create: op.value, update: op.value });
results.push({ key: op.key, status: 'ok' });
} else if (op.type === 'delete') {
await db.captures.delete({ where: { id: op.key } });
results.push({ key: op.key, status: 'ok' });
}
}
return Response.json({ results });
}
Conflict resolution strategies
| Strategy | Behavior |
|---|---|
'last-write-wins' | Highest ts field always wins — great for user preferences |
'server-wins' | Remote always overrides local — great for inventory counts |
'client-wins' | Local always overrides remote — great for offline-first notes |
(local, remote) => resolved | Custom merger function — full control |
✨ Zero-Bundle UI Components
Interactive components with 0.0 KB of JavaScript. The Nexus compiler detects when a component needs no dynamic logic and emits pure HTML + CSS instead.
import {
renderAccordion,
renderTabs,
renderTooltip,
renderModal,
renderProgressRing,
} from '@nexus_js/ui';
// All of these compile to 0 bytes of JS — pure CSS interaction
Accordion (CSS details/summary)
const html = renderAccordion([
{ title: 'What is Nexus?', body: 'The definitive full-stack framework.' },
{ title: 'Does it need React?', body: 'No. Zero React dependency.' },
]);
Tabs (CSS :target trick)
const html = renderTabs([
{ id: 'js', label: 'JavaScript', content: '...' },
{ id: 'ts', label: 'TypeScript', content: '...' },
]);
Component catalogue
| Component | CSS Technique | JS KB |
|---|---|---|
renderAccordion | <details> / <summary> | 0 |
renderTabs | CSS :target selector | 0 |
renderTooltip | CSS :hover + [role=tooltip] | 0 |
renderModal | CSS :target + <dialog> | 0 |
renderProgressRing | Pure SVG stroke-dashoffset | 0 |
🛡️ Security by Default
Nexus treats security as a compilation concern, not a runtime afterthought. Compiler guardrails, action hardening, and optional Hardened Mode headers stack with zero ceremony for the basics.
1 — Anti-CSRF for server actions (dual tier, v0.7.5+)
Tier 1 (default): action requests must include the custom header x-nexus-action: 1. callAction and friends send it automatically; simple cross-origin form posts cannot forge it. Tier 2 (when x-nexus-action-token is present): the server validates the HMAC token — session-bound, single-use, ~15 minute TTL, with a small clock-skew guard against far-future iat. Origin: null (opaque contexts) is rejected. Set NEXUS_SECRET in production so tokens cannot be forged.
// Tier 1: fetch-based callers send x-nexus-action (blocks classic form CSRF)
// Tier 2 (optional token path): HMAC validation before your handler
await validateActionToken(token, sessionId, secretKey);
// Fails on: wrong HMAC | expired (~15 min) | already used | clock skew > 5s ahead
2 — Ghost Wall: Secret Leak Detection
The Nexus compiler scans every .nx file. If a server-side variable matching *_SECRET, *_KEY, or process.env.* is referenced inside a client island, the build fails immediately with a pedagogical error.
[Nexus Guard] ⛔ SECRET LEAK DETECTED
File: src/routes/dashboard/+page.nx
Variable: STRIPE_SECRET_KEY (matches pattern *_SECRET)
Found in: island script block (line 42)
Server secrets must never reach the client bundle.
Wrap sensitive logic in a Server Action instead.
3 — XSS Auto-Encoding
@nexus_js/serialize automatically HTML entity-encodes all strings flowing from server to client islands. No developer action required.
// Before serialization (server side):
{ name: "<script>alert(1)</script>" }
// After @nexus_js/serialize.encode (island receives):
{ name: "<script>alert(1)</script>" }
4 — Per-Action Rate Limiting
Limiters are registered once per action and reused across requests (sliding window state persists — not recreated per hit).
export const captureAction = createAction({
rateLimit: { window: '1m', max: 3 }, // sliding-window (default: per IP via request)
schema: z.object({ pokemonId: z.number().int().positive() }),
async handler({ pokemonId }, ctx) {
const userId = ctx.locals.userId as string | undefined; // set in your auth middleware
if (!userId) throw new Error('Unauthorized');
return await capturesRepo.create({ pokemonId, userId });
},
});
// 429 response with Retry-After header is automatic on limit breach
5 — Hardened Mode (Security Headers)
With security.hardened: true, the server adds baseline headers (X-Frame-Options, nosniff, Referrer-Policy, Permissions-Policy, HSTS in production). There is no per-field security.headers map on NexusConfig yet — add extra headers in your host or middleware if you need a custom CSP.
import type { NexusConfig } from '@nexus_js/cli';
export default {
security: {
hardened: true,
shieldLite: false,
},
} satisfies NexusConfig;
🔍 nexus audit
A static analysis + dependency security scanner that runs both in CI and live during nexus dev. It catches what npm audit misses.
nexus audit # Full scan — code + deps
nexus audit --ci # Exit 1 on any critical finding (for pipelines)
nexus audit --json # Machine-readable output
What it scans
| Check | Source | Severity |
|---|---|---|
| Secret variables in client islands | Compiler / Guard | 🔴 Critical |
| Forms without input validation schema | Static AST | 🟠 High |
| Routes missing auth middleware | Route manifest | 🟠 High |
| Missing security headers in production | nexus.config.ts | 🟡 Medium |
| Dependency CVEs (OSV database) | @nexus_js/audit engine | Varies |
| Supply chain risks (npm registry) | @nexus_js/audit engine | 🟡 Medium+ |
| Open redirect patterns | Static AST | 🟠 High |
Override policy
Acknowledge a known CVE with a mandatory reason and an expires date. After the expiry, the build fails again automatically.
import type { NexusConfig } from '@nexus_js/cli';
export default {
security: {
hardened: true,
allowVulnerable: {
'some-pdf-lib': {
cve: 'CVE-2025-1234',
reason: 'Used only at build-time; never ships to the browser.',
expires: '2026-06-01',
},
},
},
} satisfies NexusConfig;
⛓️ Supply Chain Guard
@nexus_js/audit queries the npm registry to detect high-risk dependency patterns before they become your problem.
Risk signals detected
| Signal | Risk Level | Why it matters |
|---|---|---|
| Single maintainer package | 🟡 Medium | One compromised account = all users affected |
| Package published < 30 days ago | 🟡 Medium | Insufficient time for community review |
| No public repository | 🟠 High | Code cannot be audited independently |
| 5+ versions in 24 hours | 🔴 Critical | Classic typosquatting / takeover pattern |
| Abandoned (no update >3 years) | 🟠 High | Unpatched vulnerabilities accumulate |
| Recent maintainer change | 🟠 High | Account takeover via package transfer |
Build-time blocking (Vite plugin)
import { nexus, nexusSecurity } from 'vite-plugin-nexus';
export default {
plugins: [
nexus(),
nexusSecurity({
mode: 'block', // 'off' | 'warn' | 'block' | 'paranoid'
// 'block' → throws build error on critical CVEs
// 'paranoid' → throws on critical + high + supply-chain risks
}),
],
};
OSV CVE scanning (offline-first)
Nexus uses the Google OSV API and caches results in ~/.nexus/cache/osv/ for 24 hours. Audits work fully offline after the first run.
# Background audit runs silently when you start nexus dev
$ nexus dev
◆ Nexus v0.6.0 ready on http://localhost:3000
[Nexus Audit] Scanning 47 direct dependencies...
⚠ lodash@4.17.20 HIGH CVE-2025-3456 — Prototype Pollution
→ Run `nexus fix` to update to 4.17.21
🔧 nexus fix
One command auto-remediates vulnerable dependencies. It reads OSV fixedIn data, updates only the affected packages, re-installs, and re-audits to confirm.
nexus fix # Fix critical + high CVEs
nexus fix --dry-run # Preview what would change, no writes
nexus fix --force # Also fix medium + low severity
How it works
$ nexus fix
[Nexus Fix] Scanning for patchable vulnerabilities...
lodash@4.17.20 → 4.17.21 (CVE-2025-3456 fixed)
axios@0.21.1 → 1.6.0 (CVE-2025-7890 fixed)
[Nexus Fix] Applying patches via pnpm...
pnpm add lodash@4.17.21 axios@1.6.0
[Nexus Fix] Re-auditing...
✅ 0 critical, 0 high vulnerabilities remaining.
Nexus auto-detects your package manager (pnpm, npm, or yarn) and preserves your existing semver ranges when possible.
📋 Changelog
Build ID contract, production error masking, layout/page output split
.nexus/build-id.jsonon everynexus build; optionalNEXUS_BUILD_IDin CI- Server actions:
x-nexus-build-idmust match → else 412BUILD_MISMATCH;callActionreloads on mismatch window.__NEXUS_BUILD_ID__injected in HTML; stale tabs recover after deploy- Production: masked action errors with
errorIdunlessNEXUS_EXPOSE_ERRORS=true - CSRF token clock-skew guard; JSON body complexity limits before parse;
isSafeUrl()for SSRF-safe fetch wrappers - Root
+layoutvs+page→index._layout.js/index.js(no overwrite)
Server action security & production path fixes
- Dual-tier CSRF:
x-nexus-actionheader + optional HMAC token; compiler no longer disables CSRF on sidecars - Rate limit registry (persistent sliding window);
USED_TOKENSTTL map;NEXUS_SECRETprod warning Origin: nullrejected; action name allowlist; max body size (10 MB default);Varyon public HTML- Dev endpoints restricted to loopback
Origin;compileLib+.nexus/libfor production$lib
Integrated Dependency Auditing & Supply Chain Guard
- New package
@nexus_js/audit— OSV CVE scanning with offline-first cache - Supply chain risk analysis (single maintainer, rapid versions, abandoned packages)
nexusSecurityVite plugin — build-time CVE blocking with 4 severity modesnexus fixCLI — automated one-command vulnerability remediationallowVulnerableoverride policy with mandatoryexpiresdate- Background CVE audit integrated into
nexus devstartup
Security by Compilation
- Action security foundation: HMAC tokens + compiler guardrails; dual-tier CSRF (custom header + optional token) shipped in v0.7.5
- Ghost Wall: compiler blocks builds when server secrets reach client bundles
- XSS auto-encoding in
@nexus_js/serializefor all server→client strings - Per-action rate limiting with sliding window & RFC 6585
Retry-Afterheaders nexus auditCLI — static code analysis + dependency CVE scanning- Hardened Mode: automatic security headers (CSP, HSTS, X-Frame-Options)
Multi-Tenant, Local-First, Zero-Bundle
- Native multi-tenant support in
@nexus_js/routerwith typedtenantcontext @nexus_js/sync—$localSyncrune with IndexedDB + background server sync@nexus_js/ui— 5 zero-JS CSS-only interactive components- Multi-Tenant Row Level Security warnings in
@nexus_js/dbadapter
Elite Observability, Edge-State Sync, AI Prefetch
- Nexus Logger — ANSI terminal logs + browser DevTools bridge (Server→Client)
- Nexus Connect — SSE pub/sub real-time state sync (
$socketrune) - Nexus Guard — compiler-level secret leak detection
- Nexus AI — micro-ML predictive prefetch with Network Information API awareness
nexus add— Nexus Blocks marketplace CLI- Performance Score & A11y Checker in browser console
HMR, Vite-style Logging, Pokédex Demo
- Hot Module Replacement via Vite plugin
- Colored ANSI server logs (○ static, λ dynamic, ▲ action)
- Pokédex example project with Shield Cache, Evolution Chain streaming, Battle Mode
nexus studio— local dev dashboard at/_nexus
Initial Architecture
- Islands Architecture, Svelte 5 Runes, Server Actions, File-based Routing
- PPR, Edge-Cache headers, Global State Store, SPA Navigation (Morphing)
- BYOD Database adapters,
@nexus_js/serialize,@nexus_js/assets
Framework Comparison
How Nexus compares to mainstream full-stack and meta-frameworks — including B2B-style multi-tenancy, where Nexus is intentionally opinionated.
Legend: ✓ first-class · ✗ not a goal / not built-in · ⚠ partial or DIY · — varies by hosting
| Feature | Nexus | Next.js 15 | Astro 5 | SvelteKit | Remix | Nuxt 3 |
|---|---|---|---|---|---|---|
| Islands Architecture | ✓ | ✗ | ✓ | ✗ | ✗ | ⚠ islands module |
| Svelte 5 Runes | ✓ | ✗ | ✗ | ✓ | ✗ | ✗ |
| Server Actions / mutations | ✓ | ✓ | ✗ | ✓ | ⚠ forms + actions | ✓ |
| SPA Navigation (Morphing) | ✓ | ✓ | ⚠ partial | ✓ | ✓ | ✓ |
| Streaming SSR | ✓ | ✓ | ✗ | ✗ | ✓ | ✓ |
| Auto Edge-Cache Headers | ✓ | ✗ | ✗ | ✗ | ⚠ | ⚠ |
| Global State Store | ✓ | ✗ | ✗ | ✗ | ✗ | ⚠ Pinia |
| E2E Type Safety | ✓ | ⚠ partial | ✗ | ⚠ partial | ⚠ partial | ⚠ partial |
| Bundle Budget Analyzer | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ |
| BYOD Database | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Zero config (DX) | ✓ | ⚠ | ✓ | ⚠ | ⚠ | ⚠ |
| React / Vue default runtime | ✗ (Svelte) | React | agnostic | ✗ (Svelte) | React | Vue |
| Native multi-tenant (subdomain / domain) | ✓ | ✗ DIY | ✗ DIY | ✗ DIY | ✗ DIY | ✗ DIY |
| Local-First Sync (IndexedDB) | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ |
| Zero-Bundle CSS UI primitives | ✓ | ✗ | ⚠ manual | ✗ | ✗ | ✗ |
| Anti-CSRF for mutations (built-in) | ✓ | ⚠ | ✗ | ⚠ | ⚠ | ⚠ |
| Secret leak detection (build) | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ |
| Integrated CVE / supply-chain audit | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ |
| Predictive prefetch / edge state | ✓ | ✗ | ✗ | ✗ | ⚠ prefetch | ⚠ |
Takeaway: Next, Remix, Nuxt, and SvelteKit are excellent — but if you are standardizing on tenant subdomains + custom domains + cache isolation in one framework contract, Nexus documents and types that path explicitly. Other stacks can do multi-tenancy at the infrastructure layer; Nexus aims to make it a first-class routing and ctx concern.
Package Reference
| Package | Description |
|---|---|
@nexus_js/compiler | .nx → JS transform, AOT CSS scoping, island preload scanner |
@nexus_js/runtime | Runes, island hydration, store, navigation, cache, $optimistic, $sync |
@nexus_js/server | HTTP server, SSR renderer, streaming SSR, Server Actions, error boundaries |
@nexus_js/router | File-based route manifest builder |
@nexus_js/cli | nexus CLI + Nexus Studio dashboard + create-nexus scaffolder |
@nexus_js/assets | AVIF/WebP image optimization, font preloading and inlining |
@nexus_js/head | defineHead() server-side + useHead() reactive client-side |
@nexus_js/middleware | CORS, rate limit, auth, geo, security headers — Cloudflare/Vercel adapters |
@nexus_js/serialize | SuperJSON-like: Date, Map, Set, BigInt, RegExp, URL, Uint8Array, Error |
@nexus_js/types | E2E type generation for routes, params, and Server Actions |
@nexus_js/testing | renderSSR, mountIsland, createActionTestHarness for Vitest |
vite-plugin-nexus | Vite plugin for HMR, CSS preprocessing, island manifest emission, CVE blocking |
@nexus_js/db | BYOD DB adapter: Prisma, Drizzle ORM, libSQL/Turso, or custom |
@nexus_js/sync | $localSync rune — IndexedDB offline writes + background server sync + conflict resolution |
@nexus_js/ui | Zero-Bundle CSS-only interactive components (Accordion, Tabs, Modal, Tooltip, Progress Ring) |
@nexus_js/audit | OSV CVE scanning, supply chain risk analysis, override policy with auto-expiry |
@nexus_js/connect | SSE pub/sub real-time state sync — $socket(topic) rune |
@nexus_js/ai | Micro-ML predictive prefetch with Network Information API awareness |
@nexus_js/marketplace | nexus add <block> — official Nexus Blocks installer |
Nexus — MIT License © 2026 Nexus Contributors
Framework (nexus) ↗
· This site (nexusjs-site) ↗