Personalização
Substitua componentes, adicione ilhas interativas, monte páginas personalizadas, instale componentes do registro ou faça eject completo quando precisar de controle total.
Substituições de componentes
Adicione um components.ts (ou components.tsx) à raiz do seu projeto e exporte defineComponents. O mapa mdx substitui um componente integrado ou adiciona um novo — disponível em todas as páginas .mdx sem nenhum import.
import { defineComponents } from "blume";
import Callout from "./components/Callout.astro";
import Pricing from "./components/Pricing.astro";
export default defineComponents({
mdx: {
Callout, // replace the built-in Callout
Pricing, // add a new <Pricing /> component
},
});
As chaves são os nomes que você escreve no MDX (<Callout>, <Pricing>). Use o nome de arquivo .tsx quando importar componentes React.
Uma tag com inicial maiúscula que não seja um componente integrado, uma ilha nem uma substituição faz a página falhar ao ser renderizada, então o blume dev, o blume build e o blume check emitem um aviso sobre ela antes, como BLUME_UNKNOWN_COMPONENT, uma vez por tag, indicando todas as páginas que a utilizam.
Forma de referência
Toda substituição — em mdx ou layout — aceita três formas:
import { defineComponents } from "blume";
import Callout from "./components/Callout.astro";
export default defineComponents({
mdx: {
Callout, // 1. an imported component
Note: "./components/Note.astro", // 2. a path string (resolved from the project root)
Chart: { component: "./components/Chart.tsx", client: "load" }, // 3. a descriptor
},
});
O Blume lê o components.ts estaticamente — ele nunca executa o arquivo —, então essas três formas são as únicas que ele aceita. Uma função ou expressão inline, um componente declarado no próprio arquivo, um spread, uma chave computada ou um client que não seja uma das strings de modo abaixo gera um erro BLUME_COMPONENTS_INVALID que indica a entrada: o blume dev o reporta no terminal e no overlay do navegador, e o blume build falha.
A forma de descritor adiciona um modo de hidratação para que um componente interativo React/Vue/Svelte envie seu JavaScript e ganhe vida no cliente. Sem um modo client, um componente de framework é renderizado como HTML estático — o Blume exibe um aviso de build quando detecta um desses, já que isso costuma ser um engano.
client |
Hidrata |
|---|---|
"load" |
Imediatamente ao carregar a página |
"idle" |
Quando a thread principal estiver ociosa |
"visible" |
Quando entrar na área visível ao rolar |
"media" |
Quando uma consulta media corresponder (adicione media: "(min-width: 40rem)") |
"only" |
Apenas no cliente, nunca renderizado no servidor |
Uma entrada mdx com um modo client é uma ilha: ela fica disponível em todas as páginas e é hidratada exatamente como um componente colocado na pasta islands/. A pasta continua sendo o caminho sem configuração — recorra a uma entrada no components.ts quando quiser um nome diferente do nome do arquivo, uma consulta media ou manter as ilhas junto das suas outras substituições.
Tipando uma substituição
Ao substituir um componente integrado, importe o tipo das suas props de blume/components para que seu componente respeite o contrato — os tipos derivam dos próprios componentes, então nunca ficam defasados:
import type { CalloutProps } from "blume/components";
export default function Callout(props: CalloutProps) {
// …your own callout, same props as the built-in
}
Os tipos de props são exportados para os componentes de conteúdo (CalloutProps, CardProps, TabsProps, StepsProps, BadgeProps e outros).
Slots de layout
O mapa layout substitui uma parte da estrutura visual do Blume pelo seu próprio componente. Cada substituição recebe as mesmas props do componente integrado que ela substitui, então você pode envolver o padrão ou começar do zero.
import { defineComponents } from "blume";
import Footer from "./components/Footer.astro";
import Logo from "./components/Logo.astro";
export default defineComponents({
layout: {
Logo, // brand mark + title in the header
Footer, // site-wide footer, in place of the one `footer` configures
},
});
Slots conectados (os nomes diferenciam maiúsculas de minúsculas; qualquer outra chave em layout não é renderizada em lugar nenhum, e o Blume emite um aviso sobre isso):
| Slot | Substitui | Props |
|---|---|---|
Layout |
Toda a estrutura da página (RootLayout) |
Tudo o que o layout integrado recebe, mais o mapa layout |
Header |
A barra de navegação superior | site, logo, navigation, route, searchEnabled, … |
Logo |
O link da marca (símbolo + título) no cabeçalho | site, logo, locale |
Search |
O gatilho de busca do cabeçalho + modal | navigation, strings, locale, assistantEnabled |
Sidebar |
A árvore de navegação principal | items, currentRoute |
MobileNav |
A navegação dentro da gaveta mobile (usa Sidebar por padrão) |
items, currentRoute |
Breadcrumbs |
A trilha de navegação | crumbs |
TableOfContents |
O sumário desta página, na coluna da direita (variant: "desktop") e no menu suspenso acima do conteúdo em telas mais estreitas ("mobile") |
headings, title, variant |
Pagination |
Os links de anterior/próximo no rodapé | prev, next, strings |
Feedback |
A avaliação “Esta página foi útil?” abaixo do artigo (renderizada apenas quando feedback está ativado) |
strings, comments |
PageHeader |
Um ponto de injeção acima do artigo (sem componente integrado) | page, headings, route |
PageFooter |
Um ponto de injeção abaixo do artigo (sem componente integrado) | page, headings, route |
Footer |
O rodapé do site após a grade de conteúdo: links, perfis sociais, o link do repositório e as Configurações de cookies | footer, locale, site, navigation, ui |
A variante mobile também recebe as ações da página como filhos (um elemento vazio para o qual as ações são movidas a partir da coluna da direita abaixo de 1.280px), então renderize um <slot /> nela para mantê-las em telas mais estreitas. Ela também é renderizada em páginas sem títulos, com headings vazio, para que as ações continuem aparecendo ali.
PageHeader e PageFooter não têm componente integrado — eles não renderizam nada até você defini-los, o que os torna pontos de injeção convenientes para um banner promocional ou uma nota de “última atualização”. O PageFooter é renderizado no fim de todas as páginas que o Blume gera a partir do seu conteúdo, incluindo as páginas de operações da API, com Feedback e Pagination depois dele. Uma página no modo custom ou frame descarta essa área de fim de página, junto com seu título e sua trilha de navegação. Uma página personalizada em pages/ renderiza o layout em que ela mesma se envolve: o RootLayout aceita substituições de slots por meio da sua prop layout, e o PageLayout não tem área de fim de página. O Footer integrado contém os únicos links para o seu repositório e seus perfis sociais, então uma substituição de Footer (o lugar para um rodapé de marketing que a configuração não consegue expressar) deve mantê-los. Com consent definido, mantenha também uma forma de reabrir o consentimento: qualquer elemento com data-blume-consent-open funciona como o link de Configurações de cookies.
Os slots de layout aceitam as mesmas três formas de referência das substituições MDX, então um slot pode ser uma string de caminho ou um descritor hidratado ({ component, client }) quando você quiser um cabeçalho ou rodapé interativo.
Ilhas interativas
Para UI interativa (React, Vue ou Svelte), coloque um componente em uma pasta islands/ e use-o em qualquer página MDX — o Blume o hidrata para você, sem necessidade de wrapper ou registro:
import { useState } from "react";
export default function Counter() {
const [n, setN] = useState(0);
return <button onClick={() => setN(n + 1)}>Clicked {n}</button>;
}
Use it anywhere: <Counter />
Veja Ilhas para estratégias de hidratação e configuração de frameworks.
Páginas personalizadas
Adicione arquivos .astro dentro da sua pasta pages/ para montar rotas totalmente personalizadas ao lado da sua documentação — uma landing page, uma página de preços ou um índice feito à mão. Elas mantêm sua localização, então imports relativos e getStaticPaths funcionam normalmente, e podem ler sua configuração, navegação e rotas a partir do módulo blume:data.
Veja Páginas personalizadas para o guia completo.
Registro
O blume add copia um componente mantido pelo Blume para o seu projeto como código-fonte — ele é seu e você pode editá-lo livremente. Execute-o sem argumentos para listar o que está disponível:
blume add
Instale um slot de layout (cabeçalho, barra lateral, trilha de navegação, sumário, paginação ou feedback) ou qualquer componente de conteúdo (callout, card, abas, passos, acordeão e outros):
blume add callout
blume add pagination
A cópia importa o restante do framework de blume/*, então ela renderiza exatamente como a versão integrada até você alterá-la. O blume add exibe o trecho de defineComponents para registrá-la — componentes de conteúdo em mdx, peças de layout em layout.
Integrações do Astro
Adicione qualquer integração do Astro a partir do array integrations de nível superior em blume.config.ts. Instale a integração no seu site primeiro; o Blume não a adiciona às dependências do runtime gerado nem gerencia sua compatibilidade com o Astro.
npm install @astrojs/partytown
import partytown from "@astrojs/partytown";
import { defineConfig } from "blume";
export default defineConfig({
integrations: [
partytown({
config: { forward: ["dataLayer.push"] },
}),
],
});
O Blume mantém suas integrações internas na ordem existente e, em seguida, acrescenta as suas na ordem de declaração. Ele não as ordena nem remove duplicatas, então duas integrações com o mesmo name são ambas executadas. O Blume valida que integrations é um array, enquanto o Astro valida cada entrada e reporta integrações inválidas.
Como o Blume carrega suas integrações reimportando blume.config.ts a partir da configuração gerada do Astro, em vez de copiar as instâncias, o módulo de configuração é avaliado duas vezes por execução — uma quando o Blume lê sua configuração e outra quando o Astro a carrega. Mantenha as fábricas de integração livres de efeitos colaterais (retorne a integração; não escreva arquivos nem abra conexões na construção) para que a segunda avaliação seja inofensiva.
As mesmas integrações são executadas no blume dev e no blume build. Editar blume.config.ts durante o blume dev regenera a configuração oculta do Astro e dispara um reinício de configuração; se você não vir uma integração editada surtir efeito, reinicie o blume dev. O Blume não consegue determinar quais edições de configuração afetam integrações, então, quando integrations não estiver vazio, toda edição em blume.config.ts — mesmo em um campo não relacionado — reinicia o servidor de desenvolvimento em vez de aplicar a alteração a quente. O Blume só acompanha o conteúdo de blume.config.ts, então editar um arquivo separado que ele importa não dispara essa regeneração por si só — reinicie o blume dev após tais edições. Se você fizer eject, o astro.config.mjs sob sua responsabilidade mantém uma ponte relativa para o blume.config.ts, então as integrações configuradas continuam funcionando; mais tarde, você pode movê-las diretamente para a configuração do Astro como parte de assumir a propriedade total.
Eject
Quando quiser controle total, faça o eject do runtime gerado para um projeto Astro independente:
npx blume eject --yes
O eject é um passo sem volta: o runtime oculto .blume/ se torna um aplicativo Astro normal que é seu e que você pode modificar diretamente. O pacote blume continua importável, então você mantém seus componentes, tema e processadores de Markdown.
O eject aponta os scripts do seu package.json para astro dev e astro build e adiciona os pacotes que o aplicativo Astro importa pelo nome — astro, @tailwindcss/vite, tailwindcss e @tailwindcss/typography para as folhas de estilo geradas, o cliente de busca, as integrações e o adaptador que a configuração conecta, o React quando uma ilha, um exemplo ou o assistente o utiliza, o ai para a rota do assistente e o epub-gen-memory para a exportação em EPUB — nas mesmas faixas de versão que o próprio Blume usa. Execute uma instalação antes do dev ou do build; o eject lista o que adicionou. Todo caminho no aplicativo após o eject é relativo, então ele faz o build a partir de qualquer checkout, inclusive no CI.
A partir daí, execute o aplicativo pelos próprios scripts dele (npm run dev, npm run build): o blume dev, o blume build, o blume check, o blume sync e o blume preview são interrompidos em um projeto após o eject e direcionam você para esses scripts. Executar o blume eject novamente também é recusado, já que isso sobrescreveria suas edições; passe --force para regenerar o aplicativo mesmo assim.
O que o eject mantém
As rotas que o runtime oculto serve como páginas são escritas no src/pages do aplicativo, então ele responde nas mesmas URLs: a versão em Markdown de cada página, a página 404 com suas versões /404.md e /404.json, o servidor MCP hospedado e a API de documentação em JSON em /api/docs/… e /openapi.json, para a qual o llms.txt e a página de não encontrado direcionam os agentes.
O script build do aplicativo após o eject executa um astro build puro, e os artefatos que o blume build adiciona por cima — o índice de busca (e a sincronização do índice de um provedor hospedado), llms.txt e llms-full.txt, sitemap.xml, robots.txt, agent-readability.json, os arquivos de descoberta .well-known, as Agent Skills e os arquivos de plataforma _redirects/_headers — continuam sendo produzidos: a integração do Blume no astro.config.mjs gerado pelo eject os escreve a partir do hook astro:build:done do Astro, varrendo o projeto (seu blume.config.ts e seu conteúdo) da mesma forma que a CLI fazia. O que o build após o eject não faz é o pós-processamento de adaptadores da CLI: as inserções de roteamento Accept: text/markdown da Vercel e da Cloudflare, a auditoria do bundle de funções da Vercel, o wrapper do ponto de entrada do servidor node() (os tipos de mídia e os cabeçalhos CORS dos arquivos de descoberta .well-known, o sandbox nos SVGs baixados e o status exato de cada redirecionamento), as regras de cabeçalho que um build de servidor netlify() escreve em .netlify/v1/config.json, a nomeação do Worker da Cloudflare (com base no seu projeto) e o redirecionamento .wrangler/deploy dele para executar wrangler deploy a partir da raiz do projeto, e a verificação de --analyze/--budget-*.