Routing

EmberKit usa routing basado en archivos. Los archivos bajo src/routes/ se mapean a URLs. El plugin de Vite genera virtual:emberkit-routes para el cliente; el build escribe las mismas rutas en ssr-manifest.json para SSR y pre-renderizado.

Rutas básicas

ArchivoURL
src/routes/index.tsx/
src/routes/about.tsx/about
src/routes/docs/index.tsx/docs
src/routes/docs/installation.tsx/docs/installation
src/routes/blog/[slug].tsx/blog/:slug
src/routes/docs/[...rest].tsx/docs/:rest* (catch-all)

Extensiones soportadas: .tsx, .ts, .jsx, .js, .md, .mdx.

Props del componente de ruta

Las rutas dinámicas reciben params tipados en servidor y cliente:

// src/routes/blog/[slug].tsx
import type { RouteParams } from '@emberkit/core';

export default function BlogPost({ params }: RouteParams<{ slug: string }>) {
  return <h1>Post: {params.slug}</h1>;
}

RouteParams también incluye:

  • query — search params parseados (Record<string, string | string[]>)
  • requestRequest construido desde la URL actual (navegación en cliente)

Los params catch-all usan el nombre del bracket: [...rest].tsxparams.rest.

Rutas con prefijo de locale

Para URLs internacionalizadas, añade un segmento [locale] y conecta i18n en el loader del layout:

src/routes/[locale]/index.tsx   →  /en, /es
src/routes/[locale]/about.tsx   →  /en/about

Consulta Internacionalización para catálogos JSON, createI18nFromGlob y resolveLocaleFromRequest.

Layouts

_layout.tsx envuelve las rutas en el mismo árbol de directorios:

// src/routes/_layout.tsx — envuelve todas las rutas
import type { RouteComponent } from '@emberkit/core';

const RootLayout: RouteComponent = ({ children }) => (
  <div className="app">
    <nav>...</nav>
    <main>{children}</main>
  </div>
);

export default RootLayout;
// src/routes/docs/_layout.tsx — envuelve /docs/*
export default function DocsLayout({ children }: { children: unknown }) {
  return (
    <div className="docs">
      <aside>Sidebar</aside>
      <article>{children}</article>
    </div>
  );
}

Archivos especiales (no son segmentos de URL): _layout.tsx, _error.tsx, _loading.tsx y todo lo que esté bajo _api/.

Páginas de error personalizadas

ArchivoPropósito
src/routes/404.tsxSe muestra cuando ninguna ruta coincide (404)
src/routes/500.tsxSe muestra cuando el renderizado lanza un error (500)
src/routes/_error.tsxError boundary a nivel de ruta (planificado / errores de layout)

Si omites 404.tsx o 500.tsx, EmberKit usa páginas de error integradas en SSR y en la navegación del cliente. El plugin de Vite las expone como notFoundRoute y errorRoute en virtual:emberkit-routes; pásalas a render() junto con routes. Un 404.tsx personalizado recibe pathname; un 500.tsx personalizado recibe error: { status, message, error }. Añade los archivos de la tabla para personalizarlas, o importa DefaultNotFoundPage / DefaultServerErrorPage desde @emberkit/core.

// src/routes/404.tsx
export default function NotFound() {
  return <h1>404Not found</h1>;
}

Metadatos de ruta

Exporta metadata para el inyector de head SSR integrado:

export const metadata = {
  title: 'Blog — My App',
  description: 'Latest posts',
};

export default function BlogIndex() {
  return <h1>Blog</h1>;
}

Enlaces y navegación programática

import { navigate, preload } from '@emberkit/core';

// Navegación en cliente (opcionalmente con View Transitions)
navigate('/about');
navigate('/settings', { replace: true });

// Hint de prefetch
preload('/about');

Prefiere <a href="/path"> para enlaces que deban funcionar sin JavaScript y participen en SSR.

Loaders de ruta

Los loaders devuelven un LoaderResult<T> discriminado:

import type { LoaderFunction } from '@emberkit/core';
import { createLoaderData } from '@emberkit/core';

interface PostData {
  title: string;
  content: string;
}

export const loader: LoaderFunction<PostData> = async ({ params }) => {
  const res = await fetch(`/api/posts/${params.slug}`);
  if (!res.ok) {
    return {
      error: { code: 'NOT_FOUND', message: 'Post not found', status: 404 },
    };
  }
  const post = await res.json();
  return createLoaderData(post);
};

export default function BlogPost({ data }: { data: PostData }) {
  return (
    <article>
      <h1>{data.title}</h1>
    </article>
  );
}

Usa createLoaderData u objetos { data } / { error } que coincidan con LoaderResult<T>.

Estados de carga

// src/routes/_loading.tsx
export default function Loading() {
  return <div className="spinner">Loading...</div>;
}

Rutas API

Los handlers solo de servidor bajo src/routes/_api/ quedan excluidos del manifiesto de páginas:

// src/routes/_api/hello.ts
export async function GET(request: Request) {
  return new Response(JSON.stringify({ message: 'Hello' }), {
    headers: { 'Content-Type': 'application/json' },
  });
}

Comportamiento SSR y estático

Tipo de rutaBuild hybridPetición hybrid
Estática (/about)Pre-renderizada a dist/about/index.htmlServida como HTML estático
Dinámica (/blog/:slug)No pre-renderizadaRenderizada vía entry-server.js

Consulta SSR y SSG para detalles de modos.

Próximos pasos

  • SSR y SSG — Pipeline de build y modos de renderizado
  • Signals — Estado reactivo
  • Head — Etiquetas head declarativas
  • Hidratación — Bindings en cliente tras SSR