Hooks
O único hook de build que o duxt emite, o que ele entrega e por que existe apenas um.
O duxt emite um hook. É um hook de build do Nuxt, dispara uma vez por build e entrega todas as páginas do site como registos de pesquisa 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);
});
}
});
Um exemplo completo — o esquema da coleção, o upsert, a remoção do que já não existe e a chave que nunca pode chegar a uma página — está em Envie suas páginas para o Typesense.
duxt:search:records
(records: readonly DuxtSearchRecord[]) => void | Promise<void>
Quando dispara. Depois de o Content ter analisado cada fonte, cada versão,
cada idioma e cada secção gerada — incluindo os repositórios remotos que
descarregou — e antes de o Nitro prerenderizar ou empacotar o que quer que
seja. Um listener ainda pode escrever um ficheiro em public/ e vê-lo chegar à
saída.
O que é um registo. Uma página, mais um por cada cabeçalho que ela tenha.
| Campo | Tipo | O que contém |
|---|---|---|
id | string | A identidade do registo, e uma chave primária válida em qualquer motor |
url | string | Onde o leitor aterra — segmento de idioma e âncora incluídos |
title | string | O cabeçalho que o registo cobre, ou o título da página no primeiro |
content | string | As palavras sob esse cabeçalho, sem marcação e com espaços normalizados |
source | string | De que fonte veio, como segmentos de URL sem a versão |
version | string | O rótulo da versão, onde a fonte tem versões — ausente onde não tem |
locale | string | O idioma em que o conteúdo está escrito |
id é um resumo hexadecimal em vez do URL, porque o Meilisearch só aceita
A-Za-z0-9_- como identificador de documento. Deriva de url, por isso é igual
em qualquer máquina e mantém-se enquanto a página ficar onde está — que é
exatamente o que permite a um fornecedor fazer upsert em vez de reconstruir.
source é uma identidade, não um endereço: / para a documentação servida
na raiz, /harbour para um segundo repositório, /harbour/api para uma secção
gerada lá dentro. A versão fica deliberadamente de fora, para que uma faceta
construída sobre source sobreviva a um lançamento e version, ao lado, seja o
que restringe a uma só.
locale é o idioma do conteúdo, não o idioma de interface em que se está a ler.
Um site com fontes não traduzidas obtém um conjunto de registos mesmo
servindo sete idiomas — o mesmo parágrafo indexado sete vezes é uma resposta
repetida.
Constrói ou envia o teu próprio índice a partir dela. Não reescrevas um registo nem ordenes o array no lugar: todos os outros listeners recebem o mesmo objeto, e uma edição de passagem seria herdada pelo seguinte sem nada o indicar.
O que ele deliberadamente não leva
- Nenhum excerto. Um excerto é uma decisão sobre uma lista de resultados, e a lista de resultados é tua.
- Nenhum nome de coleção. Isso é a contabilidade interna do índice do duxt, e um documento com essa chave parte no dia em que uma fonte ganha um idioma.
- Nenhum rótulo de exibição.
label,namee a legenda da versão são prosa para quem lê, traduzida por idioma. Um registo não é lido por ninguém.
O que fica fora do índice
Uma página que escreveu search: false não produz
registo nenhum — a mesma exclusão que a caixa de pesquisa, a ferramenta MCP
search_docs e ambos os índices llms já respeitam. O mesmo vale para uma fonte
traduzida num idioma que duxt.locales não serve, porque indexá-la seria
oferecer ao leitor um URL que devolve 404.
Onde um site serve duas regiões de um mesmo idioma sobre uma só árvore —
pt-PT e pt-BR a ler a mesma pasta pt/ —, os registos endereçam a primeira.
Os dois URL levam as mesmas palavras, por isso indexar ambos duplicaria o índice
para dizer duas vezes o mesmo; um fornecedor que queira levar o leitor à sua
própria região troca o segmento, o que o duxt não pode fazer porque não sabe
quem está a pesquisar.
E quando ninguém está a ouvir, o duxt não constrói carga nenhuma: um site sem fornecedor não paga nada por o hook existir.
Por que existe apenas um
Uma extensão distribuível do duxt é uma camada Nuxt, não um objeto plugin:
export default defineNuxtConfig({
extends: ['@kirchdev/duxt', 'duxt-typesense']
});
Isso já leva componentes, predefinições de app.config, coleções de
content.config.ts, rotas de servidor, ferramentas MCP e um módulo próprio,
num pacote com versão própria. Uma API duxt.plugins reimplementaria a
resolução de camadas dentro de uma camada — e um registo onde um plugin escreve
no momento do import bate na mesma parede que moldou
sectionTypes: content.config.ts e o módulo do
duxt são carregados por carregadores diferentes, por isso um registo mutável
onde ambos escrevessem seriam dois registos. Um hook de build do Nuxt não tem
esse problema, porque só a metade do módulo chega a vê-lo.
Por isso a superfície mantém-se pequena de propósito, e cada uma das outras entradas continua a ser a resposta certa à sua própria pergunta:
| Queres mudar | Usa |
|---|---|
| Uma cor, um tipo de letra, um espaçamento | Um token CSS — Sobrepor o tema |
| O que um componente desenha | Sombreá-lo, ou um slot onde exista |
| O que o site diz sobre si próprio | A chave duxt de app.config — Configuração |
| Um novo tipo de secção gerada | sectionTypes — Fontes |
| O que uma página desenha à sua volta | Frontmatter — Frontmatter |
| Onde o texto do site é indexado | Este hook |
O nome de um hook é superfície pública: renomeá-lo é uma alteração incompatível, e um que não se usa já não se consegue remover. Por isso um novo chega quando existe um consumidor que não se consegue escrever sem ele — não por antecipação, e não porque uma costura pareça vir a dar jeito um dia.