Nexanexa
← Referencia

Núcleo y páginas

Rutas, layouts, y cómo se declara una página de Nexa de arriba a abajo.

Rutas por archivo

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

Cada .tsx bajo src/pages/ es una ruta real. [slug].tsx captura un segmento dinámico; [locale] es el segmento especial que activa i18n en toda la rama.

Una página, de arriba a abajo

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 son objetos/plantillas literales — nunca se ejecutan, nexa-parser los reconoce como patrones y los resuelve él mismo contra datos reales en build/request. Por eso no pueden tener lógica: sin condicionales, sin .map(), sin funciones propias.

load() — traer datos

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

Un GET HTTP real contra NEXA_API_URL + url, con :params sustituidos por los valores reales de la ruta. El backend puede ser cualquier lenguaje — Nexa solo espera JSON de vuelta.

paths() — pre-renderizar rutas dinámicas

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

Misma forma que load, pero el backend responde con un array de sets de parámetros. nexa build genera un index.html real por cada entrada — HTML puro, sin depender de que el backend siga corriendo.

<For> — iterar listas

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>
    );
}

No es un componente ni un elemento HTML — es un patrón sintáctico fijo, igual que t("clave"). Rust clasifica el cuerpo del callback una sola vez (es una plantilla) y lo renderiza como HTML real una vez por elemento.

Link activo automático

<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" -->

Un <a href> cuyo href coincide con la ruta actual recibe aria-current="page" solo — sin escribir ningún condicional (una página Nexa no admite lógica propia). data-nexa-match="prefix" lo vuelve por prefijo, con límite de segmento.

Layouts compartidos

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, opcional, envuelve el HTML de toda página del proyecto. Se compila igual que cualquier página — no es composición: nexa-cli busca el único data-nexa-slot vacío y ahí inserta, tal cual, el HTML ya renderizado de la página.

Header con estado real: una isla en el 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>

Un onClick directo en src/layout.tsx no se soporta (ids de nodo colisionarían entre layout y página). Una isla sí — el cliente lee specifier/props/estrategia del DOM, sin entrada en el manifiesto de activación, y sobrevive la navegación SPA sin remount.