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 ctx.tenant
Resolve branding, plan limits, and feature flags in resolveMeta() once; consume tenant.meta in every server block without parsing headers by hand.
*.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 ≥ 22.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_js/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
---
// 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;
// ctx.db is your configured DB adapter
const post = await ctx.db.mutate('post', 'create', () =>
ctx.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 — same idempotency key returns cached result
registerAction('importData', importFn, { idempotent: true, timeout: 120_000 });
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 (nexus.config.ts)
import { defineNexus } from '@nexus_js/cli';
import { defineDB } from '@nexus_js/db';
import { PrismaClient } from '@prisma/client';
export default defineNexus({
db: defineDB(new PrismaClient(), {
defaultTtl: 60, // Cache every query for 60s by default
tags: (table) => [table], // Auto-tag by table name
}),
});
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
// ctx.db is fully typed from your defineNexus() config
// Cached query — result stored for defaultTtl seconds
const posts = await ctx.db.query('post', 'findMany', () =>
ctx.db.client.post.findMany({ where: { published: true } })
);
// Mutation — auto-invalidates 'post' cache tag
const post = await ctx.db.mutate('post', 'create', () =>
ctx.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>
Font Optimization
// nexus.config.ts
import { defineNexus } from '@nexus_js/cli';
export default defineNexus({
assets: {
fonts: [
{
family: 'Inter',
src: 'https://fonts.googleapis.com/css2?family=Inter',
display: 'swap',
preload: true, // Adds <link rel="preload"> in <head>
inline: false, // true → inlines CSS in <style> for zero FOUT
},
],
},
});
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/ (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
import { defineNexus } from '@nexus_js/cli';
export default defineNexus({
// Dev server
server: {
port: 3000,
host: 'localhost',
},
// Database adapter
db: defineDB(new PrismaClient()),
// Asset pipeline
assets: {
images: { formats: ['avif', 'webp'], quality: 85 },
fonts: [{ family: 'Inter', src: '...', display: 'swap' }],
},
// Internationalisation
i18n: {
defaultLocale: 'en',
locales: ['en', 'es', 'pt'],
},
// Vite plugins (fully compatible)
plugins: [
myVitePlugin(),
],
// Bundle budget (kb gzip)
build: {
budget: 50,
},
// Multi-Tenant support (v0.4)
tenant: {
mode: 'subdomain',
baseDomain: 'app.com',
},
// AI predictive prefetch (v0.3)
ai: {
prefetch: 'smart', // 'aggressive' | 'smart' | 'off'
budget: '500kb',
wifiOnly: true,
},
// Security (v0.5 / v0.6)
security: {
hardened: true,
allowVulnerable: {
'some-pdf-lib': {
cve: 'CVE-2025-1234',
reason: 'Build-time only, never ships to browser.',
expires: '2026-06-01',
},
},
},
});
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
# 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
🏢 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 |
'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.
import { extractTenant } from '@nexus_js/router';
// nexus.config.ts
export default defineNexusConfig({
tenant: {
mode: 'subdomain', // 'subdomain' | 'domain' | 'path'
baseDomain: 'app.com',
async resolveMeta(tenantId) {
return await db.tenants.findUnique({ where: { id: tenantId } });
},
},
});
// In any server block — tenant is injected automatically
const { tenant } = ctx;
// tenant.id, tenant.domain, tenant.isCustomDomain, tenant.meta
Route files
---
// tenant is already extracted — no manual parsing
const { tenant } = ctx;
const data = await db.products.findMany({
where: { tenantId: tenant.id }, // RLS enforced at the framework layer
});
---
<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 | Action tokens are bound to the same session + host as the page that rendered the island — avoids cross-subdomain replay |
📴 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. Five layers of protection are active with zero configuration.
1 — Anti-CSRF & Replay (Action Tokens)
Every island render generates an ephemeral HMAC-signed token bound to the user session. createAction validates it automatically — single-use, expired in 5 minutes.
// Automatic — you never write this manually
const token = await generateActionToken(sessionId, secretKey);
// token is injected into the island's hidden <input> by the renderer
// On the server, createAction calls this before your handler runs:
await validateActionToken(token, sessionId, secretKey);
// Throws if: wrong HMAC | expired | already used (replay detected)
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
export const captureAction = createAction({
rateLimit: { window: '1m', max: 3 }, // sliding-window per user IP
schema: z.object({ pokemonId: z.number().int().positive() }),
async handler({ pokemonId }, ctx) {
return await db.captures.create({ data: { pokemonId, userId: ctx.user.id } });
},
});
// 429 response with Retry-After header is automatic on limit breach
5 — Hardened Mode (Security Headers)
export default defineNexusConfig({
security: {
hardened: true, // enables all headers below
headers: {
'Content-Security-Policy': "default-src 'self'; script-src 'self' 'nonce-{nonce}'",
'X-Frame-Options': 'DENY',
'X-Content-Type-Options': 'nosniff',
'Referrer-Policy': 'strict-origin-when-cross-origin',
'Permissions-Policy': 'camera=(), microphone=()',
},
},
});
🔍 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.
export default defineNexusConfig({
security: {
hardened: {
allowVulnerable: {
'some-pdf-lib': {
cve: 'CVE-2025-1234',
reason: 'Used only at build-time; never ships to the browser.',
expires: '2026-06-01', // build fails again after this date
},
},
},
},
});
⛓️ 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 '@nexus_js/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
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
- Anti-CSRF & Replay: HMAC-signed single-use action tokens per island render
- 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 |
@nexus_js/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 · GitHub ↗