Ilhas
Coloque um componente interativo em islands/ e use-o em qualquer página MDX — hidratado automaticamente, sem importação por página.
O Blume renderiza sua documentação como HTML estático com zero JavaScript por padrão. Quando você precisa de algo interativo — uma demonstração ao vivo, um gráfico, um playground — você adiciona uma ilha: um componente de framework que envia JS apenas para si mesmo, apenas nas páginas que o utilizam.
A convenção islands/
Coloque um componente em uma pasta islands/ na raiz do seu projeto. O nome do arquivo se torna um componente que você pode usar em qualquer página .mdx, sem importação:
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>Clicked {count}</button>;
}
Here's a live counter: <Counter />
O nome do arquivo é o nome do componente, então ele deve ser um identificador em PascalCase — apenas letras, dígitos e sublinhados (Counter.tsx → <Counter />). Nomes de arquivo em minúsculas e nomes com traços/pontos/espaços (como Time-Picker.tsx) são ignorados com um aviso de compilação. Quando duas ilhas resolvem para o mesmo nome — Counter.tsx e Counter.vue, ou dois arquivos Counter.tsx em subpastas diferentes — o Blume mantém a primeira pela ordem dos caminhos de arquivo e ignora a outra, com um aviso de compilação.
Uma ilha é a única forma de executar código no navegador a partir de uma página. Um manipulador de eventos JavaScript em um elemento HTML simples no MDX, como <button onClick={() => open()}>, nunca é executado: a página é HTML estático, então a função é escrita no atributo como texto. O Blume emite um aviso BLUME_MDX_EVENT_HANDLER na linha em que ele aparece. Mova essa marcação para uma ilha e use a ilha na página.
Registrando ilhas em components.ts
Se você preferir manter as ilhas junto ao restante dos seus componentes — ou dar a elas um nome diferente do arquivo — registre-as com defineComponents como uma entrada mdx com um modo client. Isso é tudo o que uma ilha é: um componente MDX que é hidratado. Cada entrada está disponível em todas as páginas MDX, e uma entrada com o mesmo nome de um arquivo em islands/ o substitui.
import { defineComponents } from "blume";
import Counter from "./widgets/Counter.tsx";
export default defineComponents({
mdx: {
Counter: { component: Counter, client: "visible" }, // <Counter /> in any MDX page, hydrated
},
});
Referencie o componente por importação ou por uma string de caminho, e escolha o modo de hidratação por ilha ("media" recebe uma consulta media ao lado):
export default defineComponents({
mdx: {
Chart: { component: "./widgets/Chart.tsx", client: "only" },
},
});
Diferente da pasta, não há padrão: uma entrada mdx sem client é renderizada como HTML estático, e o Blume emite um aviso quando essa entrada é um componente React, Vue ou Svelte. O Blume lê o components.ts estaticamente, então uma entrada deve ser um componente importado, uma string de caminho ou um literal de objeto { component, client, media } — veja Personalização para as formas aceitas e o erro que você recebe caso contrário.
Hidratação
Por padrão, uma ilha usa client:visible: ela é hidratada quando o leitor a rola até o campo de visão, então uma página cheia de ilhas ainda carrega instantaneamente. Opte por uma estratégia diferente com um export const client no arquivo da ilha:
// Skip server rendering entirely — for components that touch the DOM/window.
export const client = "only";
export default function Chart() {
/* ... */
}
Valor de client |
Hidrata | Use para |
|---|---|---|
"visible" (padrão) |
Quando rolado até o campo de visão | A maioria das ilhas |
"load" |
Imediatamente ao carregar a página | UI acima da dobra, que deve ser instantânea |
"idle" |
Quando a thread principal está ociosa | Interatividade não urgente |
"only" |
Apenas no cliente, nunca renderizado no servidor | Bibliotecas que precisam de window/document (gráficos, editores) |
"media" (apenas components.ts) |
Quando uma media query CSS corresponde | UI que só roda em alguns tamanhos, como um menu apenas para mobile |
"media" precisa da consulta ao lado, então está disponível apenas para uma entrada em components.ts — { component: Menu, client: "media", media: "(max-width: 50em)" }. Um arquivo em islands/ que o declara volta para "visible" com um aviso.
Frameworks
O React funciona de imediato — o Blume o ativa automaticamente assim que seu projeto contém uma ilha .tsx/.jsx.
O React Compiler está ativado por padrão sempre que o React está habilitado, então suas ilhas são memoizadas automaticamente — sem necessidade de useMemo/useCallback escritos à mão. Ele vem com o Blume; não há nada para instalar. Desative em blume.config.ts:
export default defineConfig({
react: { compiler: false },
});
Vue e Svelte também são suportados; instale a integração Astro correspondente e o Blume configura o renderizador quando encontra uma ilha .vue ou .svelte:
# Vue
npm install @astrojs/vue vue
# Svelte
npm install @astrojs/svelte svelte
<script setup>
import { ref } from "vue";
const on = ref(false);
</script>
<template>
<button @click="on = !on">{{ on ? "On" : "Off" }}</button>
</template>
As props que você passa no MDX (<Counter start={5} />) são encaminhadas ao componente, e os filhos (<Counter>label</Counter>) chegam como o slot padrão.
Hooks
As ilhas são hidratadas por conta própria, então não há contexto do React para conduzir os dados do projeto. Em vez disso, blume/hooks lê um pequeno snapshot que o layout serializa na página — sem props para repassar:
import { useBlume, usePage } from "blume/hooks";
export default function PageInfo() {
const blume = useBlume();
const page = usePage();
if (!(blume && page)) {
return null;
}
return (
<p>
You're reading <strong>{page.title}</strong> on {blume.config.title}.
</p>
);
}
| Hook | Retorna |
|---|---|
useBlume() |
{ config, navigation } do site, ou null antes da montagem |
usePage() |
{ route, title } da página atual, ou null antes da montagem |
useSearch() |
{ search, results, loading } — consulta o provedor de busca configurado |
useAssistant() |
{ ask, messages, loading, reset } — transmite a partir do endpoint do assistente |
useBlume() e usePage() retornam null até que a ilha seja montada (para que servidor e cliente renderizem o mesmo primeiro quadro) — proteja-se contra isso. O snapshot é emitido apenas em páginas que enviam React, então um site totalmente estático não paga nada.
Em páginas personalizadas construídas com PageLayout, passe clientData para que as ilhas ali possam lê-lo:
<PageLayout
clientData={{ config: data.config, navigation: data.navigation, page: { route: "/", title: "Home" } }}
{/* …other props… */}
/>