Nexanexa
← Reference

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.