Interactive islands
For what genuinely needs a real framework, without the rest of the site stopping being HTML-first.
Marking an island
<div data-nexa-island="dashboard" data-nexa-strategy="load" data-nexa-props={{ tasks: data.tasks }}>
<p>Cargando…</p>
</div>
# nexa.toml
[imports]
dashboard = "/vendor/dashboard-island.js"data-nexa-island is a string literal, resolved against nexa.toml [imports] — Rust never opens or interprets the module it points to. The fallback (the div's children) resolves with the same mechanism as any other content: data.*, t(), <For> included.
The mount() contract
export default function mount(
el: Element,
props: Record<string, unknown>,
): void | (() => void) {
el.textContent = String(props.tasks);
return () => { /* cleanup opcional */ };
}The module must default-export a mount(el, props) function that optionally returns a cleanup function. You can write it by hand with @nexa/reactivity, or mount a real framework.
Real Vue inside an island
// packages/vue-island
import { createApp } from "vue";
export function defineVueIsland(component) {
return function mount(el, props) {
const app = createApp(component, props);
app.mount(el);
return () => app.unmount();
};
}A real Vue (or other framework) component, with Rust never reading or understanding it — it runs 100% on the client. A Vue Router mounted inside coexists without conflict with Nexa's SPA navigation.
An npm library inside an island
// src/islands/markdownPreview.island.ts
import { marked } from "marked"; // import normal, se bundlea con esbuild
export default function mount(el, props) {
const textarea = document.createElement("textarea");
const preview = document.createElement("div");
const render = () => { preview.innerHTML = marked.parse(textarea.value); };
textarea.addEventListener("input", render);
el.append(textarea, preview);
render();
}Different from [imports]: an island file gets bundled in full with esbuild (--bundle --format=esm), so a normal import at the top resolves just like in any TypeScript project.
Lightweight component model (islands only)
import { mountComponent, type Component } from "@nexa/reactivity";
const Item: Component<{ label: string }> = (el, props) => {
el.textContent = props.label;
};
export default function mount(el, props) {
return mountComponent(Item, el, { label: props.label });
}Doesn't apply to Nexa Core pages. Component<P>, mountComponent(), and a ComponentContext that chains children's cleanup to the parent's — no Virtual DOM, exclusively for code inside a native island.