Frontend overview
The server-side Netron lives inside Titan itself (
@omnitron-dev/titan/netron, including all four transports). This section covers the browser side.
Browser-side packages:
| Package | Role | Framework | Size |
@omnitron-dev/netron-browser | RPC transport (HTTP + WebSocket), middleware, auth, multi-backend pool | Framework-agnostic — vanilla JS, Vue, Svelte, Solid, Angular, Lit, React, Web Workers | ~25 kB gz |
@omnitron-dev/netron-react | React hooks, query/mutation cache, auth guards, devtools | React-only — optional layer on top of netron-browser | ~15 kB gz |
@omnitron-dev/prism | Design system: MUI v9 components, blocks, layouts, forms, hooks | React-only (MUI v9 is React) — optional | Depends on selected components and peers; unused modules are discarded |
netron-browser works everywhere. Add netron-react + prism
only for React apps. For Vue / Svelte / Solid / Angular / Lit,
use netron-browser directly and wrap calls in your framework's
reactivity primitives.
How they compose
flowchart LR
Comp["React component<br/>built with Prism"]
Hook["netron-react hook<br/>(useQuery / useService)"]
Client["netron-browser<br/>NetronClient"]
Wire["HTTP / WebSocket"]
Server["Titan @Service / @Public"]
Comp --> Hook --> Client --> Wire --> ServerA typical screen:
import { DataGridBlock } from '@omnitron-dev/prism/blocks';
import { useService } from '@omnitron-dev/netron-react';
interface UserService {
list(filter: UserFilter): Promise<User[]>;
}
function UsersPage() {
const users = useService<UserService>('users');
return (
<DataGridBlock
title="Users"
columns={[
{ field: 'email', header: 'Email' },
{ field: 'role', header: 'Role' },
]}
query={({ page, sort, filter }) =>
users.list.useQuery([{ page, sort, filter }])
}
/>
);
}Three packages, one screen, no codegen.
End-to-end types
There is no schema file, no codegen step, no manual sync. The TypeScript compiler is the source of truth:
// Shared package — the contract:
export interface UsersService {
findById(id: string): Promise<User | null>;
}
// Browser — type flows through:
const users = useService<UsersService>('users');
const { data } = users.findById.useQuery([id]);
// ^? User | null | undefinedA method signature change on the server fails the build on
every caller in the same tsc pass.
Wiring an app from scratch
import { NetronReactClient, NetronProvider } from '@omnitron-dev/netron-react';
import { AuthProvider } from '@omnitron-dev/netron-react/auth';
import { PrismProvider, ProviderStack } from '@omnitron-dev/prism/core';
import { RouterProvider } from 'react-router-dom';
const client = new NetronReactClient({
url: import.meta.env.VITE_API_URL,
transport: 'http',
});
// `AuthProvider` takes a `config` (refreshEndpoint?, logoutEndpoint?,
// storage?: 'local' | 'session' | 'memory', autoRefresh?, refreshThreshold?)
// and an `onLogin` handler that performs the actual sign-in and returns
// an AuthResult.
function App() {
return (
<ProviderStack
providers={[
[NetronProvider, { client }],
[AuthProvider, {
config: { storage: 'local', autoRefresh: true, refreshEndpoint: '/auth/refresh' },
onLogin: (credentials) => signIn(client, credentials),
}],
[PrismProvider, { defaultSettings: { mode: 'dark', primaryColor: '#7c4dff' } }],
[RouterProvider, { router }],
]}
>
<Outlet />
</ProviderStack>
);
}Four providers, one app. Anything Titan-side becomes
discoverable through useService. The auth API is login / logout
(from useAuth()) with <AuthGuard> / <GuestGuard> /
<RoleGuard> / <PermissionGuard> route guards — see
netron-react / Auth for the full surface.
Multi-backend out of the box
Talking to several Netron servers? Swap NetronProvider for
MultiBackendProvider:
import { createMultiBackendClient }
from '@omnitron-dev/netron-browser';
import { MultiBackendProvider, useBackendService }
from '@omnitron-dev/netron-react';
const client = createMultiBackendClient({
baseUrl: 'https://api.example.com',
backends: {
auth: { path: '/auth', transport: 'http' },
media: { path: '/media', transport: 'http' },
analytics: { path: '/analytics', transport: 'http' },
},
routing: {
patterns: [
{ pattern: 'users.', backend: 'auth' },
{ pattern: 'objects.', backend: 'media' },
{ pattern: 'reports.', backend: 'analytics' },
],
},
});
<MultiBackendProvider client={client}>
<Outlet />
</MultiBackendProvider>
// In components:
const users = useBackendService<UserService>('auth', 'users');→ netron-react / Multi-backend.
What lives where
| You want… | Reach for |
| Theme, colors, typography | @omnitron-dev/prism/theme |
| Dashboard / auth / data-grid shell | @omnitron-dev/prism/blocks |
| Sidebar + topbar layout | @omnitron-dev/prism/layouts |
| Card / Table / Drawer / Form field | @omnitron-dev/prism or @omnitron-dev/prism/components |
| Schema-aware forms | @omnitron-dev/prism/forms |
| Persisted UI state (sidebar open, dark mode) | @omnitron-dev/prism/state |
Plain React hooks (useArray, useFocusTrap, …) | @omnitron-dev/prism/hooks |
| Type-safe RPC call inside a component | useService from @omnitron-dev/netron-react |
| Live data over WebSocket | useSubscription from @omnitron-dev/netron-react |
| Sign-in flow + route guards | @omnitron-dev/netron-react/auth + <AuthGuard> |
| Multi-backend setup | MultiBackendProvider from @omnitron-dev/netron-react |
| Raw RPC client (no React) | createClient from @omnitron-dev/netron-browser |
| Cross-tab auth sync | AuthenticationClient from @omnitron-dev/netron-browser |
| Cache, retry, circuit breaker | @omnitron-dev/netron-browser/middleware |
Bundle strategy
| Stage | Bundle goal |
| First paint (auth screen) | Load the auth screen and its providers |
| Authenticated shell | Add the dashboard layout and shared navigation after authentication |
| Per-route page | Lazy-load with React.lazy() and measure the emitted route chunks |
| Charts / editor / maps | Load engines and styles with the routes that use them |
Named imports from the root and @omnitron-dev/prism/components discard unused
modules. Public subpaths such as components/card, components/chart,
components/map, components/editor and components/emoji-picker are also
available for explicit composition and lazy loading. Only declared exports are
public; there is no wildcard mapping from every source directory.
Import each component's documented CSS entry when required. CSS remains in the
production output. The package archive check verifies that a simple Card
consumer does not retain optional engines and that selected calendar, map and
stylesheet dependencies remain. It does not set an application-wide size promise:
measure initial requests, route chunks and shared peer dependencies in the actual
production build.
Production reference
The Omnitron Console (apps/omnitron/webapp/) is built entirely
from these three packages. ~20 pages, multi-backend, full RBAC,
real-time subscriptions, devtools — see
Console for the route map.
Read on
Prism — design system in depth
netron-browser — the transport client
netron-react — React hooks