Nexanexa
← Reference

Core and pages

Routing, layouts, and how a Nexa page is declared top to bottom.

File-based routing

pages/index.tsx                      -> /
pages/about.tsx                      -> /about
pages/products/[slug].tsx            -> /products/:slug
pages/[locale]/products/[slug].tsx   -> /:locale/products/:slug

Every .tsx under src/pages/ is a real route. [slug].tsx captures a dynamic segment; [locale] is the special segment that turns on i18n for that whole branch.

A page, top to bottom

export const load = { url: "/products/:slug" };
export const paths = { url: "/products" };

export const seo = {
    title: `${data.name} | Mi Tienda`,
    canonical: `/products/${params.slug}`
};

function buy() { cart.add(data.id); }

export default function ProductPage() {
    return (
        <article>
            <h1>{data.name}</h1>
            <button onClick={buy}>Comprar</button>
        </article>
    );
}

load/paths/seo/schema are literal objects/templates — never executed; nexa-parser recognizes them as fixed patterns and resolves them itself against real data at build/request time. That's why they can't hold logic: no conditionals, no .map(), no functions of your own.

load() — fetching data

export const load = { url: "/products/:slug" };
// GET NEXA_API_URL + /products/iphone-17
// data.* queda disponible en toda la página

A real HTTP GET against NEXA_API_URL + url, with :params substituted by the route's real values. The backend can be any language — Nexa only expects JSON back.

paths() — pre-rendering dynamic routes

export const paths = { url: "/products" };
// backend responde: [{ "slug": "iphone-17" }, { "slug": "pixel-10" }]
// -> dist/products/iphone-17/index.html
// -> dist/products/pixel-10/index.html

Same shape as load, but the backend responds with an array of parameter sets. nexa build generates a real index.html for each entry — plain HTML, with no dependency on the backend still running.

<For> — iterating lists

export const load = { url: "/articles" };

export default function Articles() {
    return (
        <ul>
            <For each={data.items}>
                {(item) => (
                    <li><a href={`/articles/${item.slug}`}>{item.title}</a></li>
                )}
            </For>
        </ul>
    );
}

Not a component or an HTML element — a fixed syntactic pattern, just like t("key"). Rust classifies the callback's body once (it's a template) and renders it as real HTML once per array element.

Automatic active link

<nav>
    <a href="/">Inicio</a>
    <a href="/dashboard" data-nexa-match="prefix">Panel</a>
</nav>
<!-- en /dashboard/settings, el segundo <a> recibe aria-current="page" -->

An <a href> whose href matches the current route gets aria-current="page" on its own — no conditional to write (a Nexa page has no logic of its own to write one in). data-nexa-match="prefix" turns it into prefix matching, with a segment boundary.

Shared layouts

export default function Layout() {
    return (
        <div class="site-shell">
            <header><nav><a href="/">Inicio</a></nav></header>
            <div data-nexa-slot></div>
            <footer><p>© 2026</p></footer>
        </div>
    );
}

src/layout.tsx, optional, wraps every project page's HTML. It compiles just like any page — this isn't composition: nexa-cli finds the single empty data-nexa-slot and inserts the page's already-rendered HTML there, verbatim.

Header with real state: an island in the layout

// src/layout.tsx
<div data-nexa-island="siteHeader" data-nexa-strategy="load">
    <header><nav><a href="/">Inicio</a></nav></header>
</div>
<div data-nexa-slot></div>

A direct onClick in src/layout.tsx isn't supported (node ids would collide between the layout and the page). An island is — the client reads specifier/props/strategy from the DOM, with no activation-manifest entry, and survives SPA navigation without remounting.