Hooks
El único hook de compilación que duxt emite, qué te entrega y por qué solo hay uno.
duxt emite un hook. Es un hook de compilación de Nuxt, se dispara una vez por compilación y te entrega todas las páginas del sitio como registros de búsqueda planos.
import { defineNuxtModule } from '@nuxt/kit';
import type { DuxtSearchRecord } from '@kirchdev/duxt/search-records';
export default defineNuxtModule({
setup(_options, nuxt) {
nuxt.hook('duxt:search:records', async (records) => {
await upload(records);
});
}
});
Un ejemplo completo de uno — el esquema de la colección, el upsert, la eliminación de lo que ya no está y la clave que jamás debe llegar a una página — está en Envía tus páginas a Typesense.
duxt:search:records
(records: readonly DuxtSearchRecord[]) => void | Promise<void>
Cuándo se dispara. Después de que Content haya analizado cada fuente, cada
versión, cada idioma y cada sección generada — incluidos los repositorios
remotos que descargó — y antes de que Nitro prerenderice o empaquete nada.
Un listener todavía puede escribir un archivo en public/ y verlo aparecer en
la salida.
Qué es un registro. Una página, más uno por cada encabezado que tenga.
| Campo | Tipo | Qué contiene |
|---|---|---|
id | string | La identidad del registro, y una clave primaria válida en cualquier motor |
url | string | Dónde aterriza quien lee — segmento de idioma y ancla incluidos |
title | string | El encabezado que cubre, o el título de la página en el primero |
content | string | Las palabras bajo ese encabezado, sin marcado y con espacios normalizados |
source | string | De qué fuente procede, como segmentos de URL sin la versión |
version | string | La etiqueta de versión, donde la fuente tenga versiones — si no, ausente |
locale | string | El idioma en que está escrito el contenido |
id es un resumen hexadecimal en lugar de la URL, porque Meilisearch solo
acepta A-Za-z0-9_- como identificador de documento. Se deriva de url, así
que es igual en cualquier máquina y se mantiene mientras la página siga donde
está — que es justo lo que permite a un proveedor hacer upsert en vez de
reconstruir.
source es una identidad, no una dirección: / para la documentación
servida en la raíz, /harbour para un segundo repositorio, /harbour/api para
una sección generada dentro de él. La versión queda fuera a propósito, para que
una faceta construida sobre source sobreviva a una publicación y version a
su lado sea lo que reduce a una sola.
locale es el idioma del contenido, no el idioma de interfaz en que alguien
está leyendo. Un sitio con fuentes sin traducir obtiene un conjunto de
registros aunque sirva siete idiomas: el mismo párrafo indexado siete veces es
una respuesta repetida.
Construye o sube tu propio índice a partir de ella. No reescribas un registro ni ordenes el array en su sitio: todos los demás listeners reciben el mismo objeto, y una edición al pasar la heredaría el siguiente sin que nada lo indique.
Lo que deliberadamente no lleva
- Ningún extracto. Un fragmento es una decisión sobre una lista de resultados, y la lista de resultados es tuya.
- Ningún nombre de colección. Eso es la contabilidad interna del índice de duxt, y un documento con esa clave se rompe el día en que una fuente gana un idioma.
- Ninguna etiqueta visible.
label,namey el rótulo de versión son prosa para quien lee, traducida por idioma. Un registro no lo lee nadie.
Lo que queda fuera del índice
Una página que escribió search: false no produce
ningún registro — la misma exclusión que ya respetan el diálogo de búsqueda, la
herramienta MCP search_docs y ambos índices llms. Tampoco la produce una
fuente traducida a un idioma que duxt.locales no sirve, porque indexarla sería
ofrecer a quien lee una URL que devuelve 404.
Donde un sitio sirve dos regiones de un mismo idioma sobre un solo árbol —
pt-PT y pt-BR leyendo la misma carpeta pt/ —, los registros apuntan a la
primera. Ambas URL llevan las mismas palabras, así que indexar las dos
duplicaría el índice para decir lo mismo dos veces; un proveedor que quiera
llevar a quien lee a su propia región cambia el segmento, algo que duxt no
puede hacer porque no sabe quién está buscando.
Y cuando nadie escucha, duxt no construye carga alguna: un sitio sin proveedor no paga nada por que el hook exista.
Por qué solo hay uno
Una extensión distribuible de duxt es una capa de Nuxt, no un objeto plugin:
export default defineNuxtConfig({
extends: ['@kirchdev/duxt', 'duxt-typesense']
});
Eso ya lleva componentes, valores por defecto de app.config, colecciones de
content.config.ts, rutas de servidor, herramientas MCP y un módulo propio, en
un paquete con su propia versión. Una API duxt.plugins reimplementaría la
resolución de capas dentro de una capa — y un registro en el que un plugin
escribe al importarse choca contra el mismo muro que dio forma a
sectionTypes: content.config.ts y el módulo de
duxt los cargan cargadores distintos, así que un registro mutable en el que
escribieran ambos serían dos registros. Un hook de compilación de Nuxt no tiene
ese problema, porque solo la mitad del módulo llega a verlo.
Así que la superficie se mantiene pequeña a propósito, y cada una de las otras vías sigue siendo la respuesta correcta a su propia pregunta:
| Quieres cambiar | Usa |
|---|---|
| Un color, una tipografía, un espaciado | Un token CSS — Sobrescribir el tema |
| Lo que dibuja un componente | Sombrearlo, o un slot donde exista |
| Lo que el sitio dice de sí mismo | La clave duxt de app.config — Configuración |
| Un nuevo tipo de sección generada | sectionTypes — Fuentes |
| Lo que una página dibuja a su alrededor | Frontmatter — Frontmatter |
| Dónde se indexa el texto del sitio | Este hook |
El nombre de un hook es superficie pública: renombrarlo es un cambio incompatible, y uno sin usar ya no se puede retirar nunca. Por eso uno nuevo llega cuando existe un consumidor que no se puede escribir sin él — no por adelantado, y no porque una costura parezca que algún día hará falta.