Convenciones

Carats resuelve por convención unas cuantas rutas y nombres de archivo. Desviarse de ellas funciona hasta que deja de funcionar, así que conviene atenerse a ellas.

Carpetas de página

Cada ruta vive en su propia carpeta bajo src/client/pages, y la carpeta exporta un componente por defecto desde index.tsx junto a una hoja de estilos del mismo nombre. Las carpetas que empiezan por guion bajo no son rutas; son bloques de construcción compartidos a los que se refieren las rutas que los necesitan.

src/client/pages/
├── _layout/          # shared building block, not a route
│   ├── index.tsx
│   └── _layout.sass
├── _error/           # suspense.error
│   └── index.tsx
├── _not_found/       # suspense.notFound
│   └── index.tsx
├── home/             # '/'
│   ├── index.tsx
│   └── home.sass
├── market/           # '/market'
│   ├── index.tsx
│   └── market.sass
├── profile/          # '/profile'
│   ├── index.tsx
│   └── profile.sass
└── trade/            # '/trade/:symbol'
    ├── index.tsx
    └── trade.sass

El guion bajo inicial es lo que marca una carpeta como bloque de construcción y no como página. Mantiene los componentes compartidos fuera de en medio en el árbol de archivos y señala que no están destinados a ser enrutados.

Diseño compartido

No hay un diseño por convención de archivos como en Next.js. Un componente _layout lo importan las páginas que lo necesitan, lo que hace explícita la composición y permite que una página se quede fuera. La plantilla de referencia envuelve cada página en el mismo diseño para compartir la cabecera, el pie y el menú móvil.

// src/client/pages/profile/index.tsx
import { Burnish } from '@carats/render';
import Layout from '../_layout';
import './profile.sass';

export default Burnish(function (user: User) {
    this.head = <>
        <title>Profile for {user.name}</title>
    </>;
    return (
        <Layout>
            <h1>{user.name}</h1>
        </Layout>
    );
});

El diseño renderiza props.children donde corresponde al contenido de la página, y ese es el único mecanismo de composición. No hay árbol de rutas anidadas ni envoltura automática, así que la dependencia se ve en el import.

Páginas de error y de no encontrado

Los componentes de suspensión error y notFound son componentes corrientes. El de error recibe el Error lanzado, así que puede leer message y stack; el de notFound no recibe nada. Vale la pena envolver ambos en el diseño compartido para que un fallo siga mostrando el armazón del sitio.

DTO globales

Las interfaces colocadas en src/dto son globales, porque typeRoots apunta a esa carpeta. Eso es lo que permite escribir un culet como culetsin import, tanto en el punto de entrada del servidor como en el componente de página. Mantén estos archivos libres de imports y de código de ejecución; son solo declaraciones.

// src/dto/user.d.ts
interface User {
  id: string
  name: string
  username: string
  email: string
  phone: string
  website: string
}

La interfaz no lleva export, y eso es lo que la hace global. Con typeRoots apuntando a src/dto, culety Burnishse resuelven en cualquier archivo sin import, y un renombrado en un sitio se recoge en todos.

Hojas de estilos

Una página o componente importa su propia hoja SASS, y esa hoja importa base.sass con @use ... as *. La extensión es opcional en la ruta. Mantén los resets globales y los tokens en base.sass y nada más allí.

// src/client/pages/trade/trade.sass
@use '../../base' as *

#trade
  .trade-header
    display: flex

La extensión en la ruta de @use es opcional, y la ruta es relativa a la hoja de estilos, no al componente. base.sass es el único archivo que define tokens; todos los demás los consumen.

Nombres

Las carpetas de ruta se nombran según el segmento de la ruta, y el nombre del parámetro dinámico debe ser idéntico en la faceta y en el culet. Los archivos de culet se nombran según la acción que realizan y exportan el culet por defecto, que luego se sienta en el punto de entrada del servidor.