fromcubes-portal-react is a Node-RED module that serves live React applications. You paste JSX straight into the node, it is compiled server-side with esbuild at deploy time, styled with Tailwind CSS 4, and wired to your flows over WebSocket — no build step, no runtime compilation in the browser.
Write plain JSX in the node editor. esbuild bundles React and your code into one file at deploy time.
Utility classes are scanned server-side and served as static CSS. Autocompletion in the editor included.
The useNodeRed() hook binds msg.payload to React state and sends events back to the flow.
Define components once in fc-portal-component nodes and reuse them across every portal page.
Chart.js, D3, Three.js, PixiJS… declared per node, auto-installed and bundled at deploy.
Built-in file manager sidebar serves models, textures and fonts at /fromcubes/public/.
Requires Node-RED ≥ 4 and Node.js ≥ 18.5. Two ways to install — pick one:
Option A — Palette Manager (no terminal needed). In the editor open Menu → Manage palette → Install, search for fromcubes-portal-react and click install:
Option B — npm. Install into your Node-RED user directory (usually ~/.node-red) and restart Node-RED:
cd ~/.node-red
npm install @aaqu/fromcubes-portal-react
# restart Node-RED afterwards
Either way, three new nodes appear in the palette under the fromcubes category: portal react, react component and react utility.
The package ships ready-made examples. In the editor open Menu → Import → Examples → @aaqu/fromcubes-portal-react. Import Shared Components first (it provides the reusable UI building blocks), then Sensor Portal — the example this guide follows end-to-end.
The Shared Components flow contains only fc-portal-component nodes — Page, Header, Stat, Button and ValueBadge. Each node holds one React component; once deployed, any portal page can use them as JSX tags.
The Sensor Portal flow is the actual application: an inject node ticks every 2 seconds, a function node generates random sensor readings, and the portal-react node renders them. Anything wired out of the portal node comes from the browser (button clicks, form input).
msg.payload = {
temp: +(18 + Math.random() * 12).toFixed(1),
humidity: Math.round(35 + Math.random() * 40),
pressure: Math.round(995 + Math.random() * 35),
ts: Date.now()
};
return msg;
Double-click the Sensor Portal node. The JSX tab holds the page's App() component in a Monaco editor with JSX highlighting, Tailwind class completion and tag completion for your shared components. The URL sub-path field — set in the Properties tab next to it (here sensors) — decides where the page is served: /fromcubes/sensors.
function App() { const { data, send } = useNodeRed(); const s = data || { temp: 0, humidity: 0, pressure: 0 }; return ( <Page> <Header title="Sensor Portal" subtitle="live sensor data" /> <div className="flex-1 flex flex-col items-center justify-center p-8"> <div className="flex gap-10 flex-wrap justify-center"> <Gauge value={s.temp} min={-10} max={50} label="Temperature" unit="°C" /> <Gauge value={s.humidity} min={0} max={100} label="Humidity" unit="%" /> <Gauge value={s.pressure} min={950} max={1050} label="Pressure" unit="hPa" /> </div> <div className="mt-10 flex gap-3 items-center"> <Button onClick={() => send({ action: 'refresh' })}>Refresh</Button> <ValueBadge label="Last" value={s.ts ? new Date(s.ts).toLocaleTimeString() : null} /> </div> </div> </Page> ); }
fc-portal-component nodes.
The bundler injects only the components your JSX actually references (plus their dependencies) — everything else is tree-shaken away.
The Gauge used by this page is itself a component node living next to the flow — a plain React function component with Tailwind classes and an SVG arc:
Hit Deploy. At that moment fromcubes-portal-react:
/fromcubes/<sub-path> and a WebSocket endpoint next to it,Navigate to http://<your-host>:1880/fromcubes/sensors. The gauges animate every two seconds as the inject node fires; every open tab stays in sync because broadcast messages reach all connected clients. Clicking Refresh emits a message on the portal node's output wire — check the debug sidebar.
useNodeRed() hookEvery portal page gets one hook that is the whole runtime API:
const { data, send, user, portalClient } = useNodeRed();
| Field | Description |
|---|---|
data | Reactive value of the last msg.payload received on the node's input. Re-renders the component on every message. |
send(payload, topic?) | Emits a message on the portal node's output wire. The message carries msg._client so replies can target just this browser tab. |
user | Portal auth data forwarded by a reverse proxy (x-portal-user-* headers), or null. |
portalClient | Unique id of this session/tab, assigned by the server — useful for per-client state. |
msg._client intact to answer one tab,
strip portalClient to reach all tabs of one user, or omit msg._client entirely to broadcast.