Saltar para o conteúdo
Blume
Esc
↑↓navegar↵abrir⌘Jpré-visualizar
Nesta página

Páginas personalizadas

Monte rotas .astro totalmente personalizadas ao lado da sua documentação e leia a configuração, a navegação e o conteúdo do seu site a partir do módulo blume:data.

A maior parte de um site Blume é Markdown, mas às vezes você precisa de uma rota que não seja um documento — uma landing page, uma página de preços, um índice de blog ou changelog construído à mão, ou um painel interativo. Coloque um arquivo .astro dentro da sua pasta pages e o Blume o monta como uma rota real, bem ao lado do seu conteúdo.

Adicionar uma página

Crie uma pasta pages/ na raiz do seu projeto e adicione um arquivo .astro:

---
import data from "blume:data";
---

<h1>Pricing for {data.config.title}</h1>

O blume dev a detecta imediatamente e o blume build a pré-renderiza como HTML estático. O nome da pasta é configurável com content.pages (padrão "pages").

As páginas personalizadas mantêm sua localização original em disco, então importações relativas, importações de componentes e getStaticPaths funcionam exatamente como funcionariam em um projeto Astro comum — o Blume monta cada arquivo onde ele está, em vez de copiá-lo.

Arquivos e rotas

O caminho de cada arquivo dentro da pasta pages se torna a sua rota. index corresponde à pasta pai, e segmentos dinâmicos [param] são preservados:

Arquivo Rota
pages/pricing.astro /pricing
pages/blog/index.astro /blog
pages/blog/[slug].astro /blog/:slug
pages/changelog.astro /changelog

Uma página personalizada prevalece sobre uma rota gerada no mesmo caminho. Adicionar pages/changelog.astro, por exemplo, substitui a linha do tempo de changelog gerada pelo Blume pela sua própria.

O Blume não consegue enumerar os caminhos que uma rota dinâmica [param] atende, então essas páginas ficam de fora do sitemap, não recebem um card Open Graph gerado e são desconhecidas pelo blume validate, que reporta um link para uma delas como quebrado. Passe ogImage para o PageLayout para definir a imagem de compartilhamento delas, ou dê a cada caminho seu próprio arquivo estático (pages/compare/mintlify.astro em vez de pages/compare/[tool].astro) para ter os três.

Lendo dados do site

Importe blume:data para ler a mesma configuração resolvida, navegação, rotas e feeds que o restante do site usa:

---
import data from "blume:data";
---

<h1>All pages</h1>
<ul>
  {
    data.routes
      .filter((route) => route.indexable)
      .map((route) => (
        <li>
          <a href={route.path}>{route.title}</a>
        </li>
      ))
  }
</ul>

Dentro de um projeto Blume, o módulo é tipado automaticamente. Você também pode trazer o formato explicitamente — para helpers tipados, props ou seu próprio tsconfig — com import type { BlumeData } from "blume":

import type { BlumeData, BlumeRoute } from "blume";

const indexable = (data: BlumeData): BlumeRoute[] =>
  data.routes.filter((route) => route.indexable);

O módulo expõe:

PropriedadeTipo
configBlumeDataConfig

Configurações resolvidas do site: title, description, logo, favicon, appleIcon, banner, theme, site, repoUrl, github (owner, repo, host e a base da api REST — null quando não definido), search, i18n, mcp, assistant, og, analytics, feedback, structuredData, toc, codeThemes, codeWrap e imageZoom.

TipoBlumeDataConfig
navigationNavigation

A barra lateral, as abas e os seletores inferidos do seu conteúdo (idioma padrão).

TipoNavigation
navigationByLocaleRecord<string, Navigation>

Árvores de navegação por idioma, indexadas pelo código do idioma. Vazio a menos que o i18n esteja configurado.

TipoRecord<string, Navigation>
routesBlumeRoute[]

Todas as páginas de conteúdo: { id, path, title, locale, indexable, hidden, draft, fallback, editUrl, lastModified, alternates, collection, entryId }.

TipoBlumeRoute[]
feedsBlumeFeed[]

Feeds RSS gerados: { href, title }.

TipoBlumeFeed[]
fontCssVarsFontHead[]

As fontes configuradas para o componente <Font> do Astro no head, uma por família: { cssVariable, preloadWeights, preloadSubsets? }.

TipoFontHead[]
uiUIStrings

Textos resolvidos da interface para o idioma padrão (rótulos de busca, barra lateral e rodapé).

TipoUIStrings
uiByLocaleRecord<string, UIStrings>

Textos da interface por idioma, indexados pelo código do idioma. Vazio a menos que o i18n esteja configurado.

TipoRecord<string, UIStrings>

routes carrega metadados da página, mas não o frontmatter como type ou date. Para construir uma lista filtrada por tipo de conteúdo — um índice de blog ou changelog — combine-a com a coleção de conteúdo docs do Astro, que contém o frontmatter:

---
import { getCollection } from "astro:content";
import data from "blume:data";
import { getBlumeCollection } from "blume/runtime";

// Under i18n every locale serves a route for each entry (its translation, or
// a copy of the untranslated page), all sharing the entry's id. Key the map on
// the default locale's routes only, so each post links once, in one language.
const routes = getBlumeCollection(data, {
  locale: data.config.i18n?.defaultLocale,
});
const routeByEntry = new Map(routes.map((route) => [route.entryId, route.path]));

const posts = (await getCollection("docs"))
  .filter((entry) => entry.data.type === "blog" && routeByEntry.has(entry.id))
  .map((entry) => ({
    description: entry.data.description,
    href: routeByEntry.get(entry.id),
    title: entry.data.title,
  }));
---

<ul>
  {
    posts.map((post) => (
      <li>
        <a href={post.href}>{post.title}</a>
        <p>{post.description}</p>
      </li>
    ))
  }
</ul>

Helpers de runtime

O blume/runtime agrupa os padrões de dados mais comuns para que você não precise mexer nos detalhes internos do blume:data.

getBlumeCollection(data, query?) seleciona rotas de conteúdo — filtradas por coleção, idioma ou prefixo de caminho, com rascunhos, páginas ocultas e cópias de fallback do i18n excluídos e o resultado ordenado por caminho — que é exatamente o que um índice personalizado precisa:

---
import data from "blume:data";
import { getBlumeCollection } from "blume/runtime";

const posts = getBlumeCollection(data, { prefix: "/blog" });
---

<ul>
  {posts.map((post) => (
    <li><a href={post.path}>{post.title}</a></li>
  ))}
</ul>

<BlumePage> renderiza o corpo de uma entrada de conteúdo dentro de uma página personalizada, com os componentes MDX integrados do Blume (callouts, cards, steps…) já conectados — para destacar um documento em uma landing page ou construir um índice sob medida que mostre conteúdo real:

---
import BlumePage from "blume/components/BlumePage.astro";
import data from "blume:data";
import { getBlumeCollection } from "blume/runtime";

const [intro] = getBlumeCollection(data, { prefix: "/docs" });
---

{intro && <BlumePage id={intro.entryId} />}

Passe components para adicionar suas próprias substituições ou ilhas (que ficam no runtime gerado e não são importadas por padrão), e collection para ler de uma coleção diferente de "docs".

Usando o layout do site

O RootLayout dá a uma página personalizada toda a estrutura da documentação — cabeçalho, barra lateral, busca, índice e tema — envolvendo-a na mesma grade de 3 colunas que as páginas geradas usam. Para uma landing page ou página de marketing, essa grade atrapalha, então recorra ao PageLayout: ele fornece a estrutura do documento, o cabeçalho, o tema e as fontes, e depois um único <slot /> de largura total (sem barra lateral, sem prosa, sem índice). Um slot opcional footer é renderizado após o <main>:

---
import PageLayout from "blume/components/layout/PageLayout.astro";
import data from "blume:data";
import Footer from "./_home/Footer.astro";

const { config } = data;
---

<PageLayout
  site={{ title: config.title, description: config.description }}
  logo={config.logo}
  banner={config.banner}
  analytics={config.analytics}
  navigation={data.navigation}
  favicon={config.favicon}
  fontCssVars={data.fontCssVars}
  themeMode={config.theme.mode}
  searchEnabled={config.search.enabled}
  siteUrl={config.site}
  ogEnabled={config.og.enabled}
  page={{ title: "Acme — the fastest docs", description: config.description }}
>
  <section class="mx-auto max-w-5xl px-6 py-24">
    <h1>Build docs that fly</h1>
  </section>
  <Footer slot="footer" />
</PageLayout>

O cabeçalho que uma página personalizada recebe é o mesmo das páginas de documentação, então tudo o que vive nele vem junto: busca, alternador de tema e — quando o assistente está configurado — o gatilho dele. Nada disso precisa ser conectado página por página. Passe assistantEnabled={false} para desativar o gatilho do assistente em uma página específica, mantendo-o em todas as outras.

O seletor de idioma é a exceção. Uma página personalizada é servida apenas na sua própria rota, então o Blume não tem como saber quais outros idiomas têm uma versão dela, e o cabeçalho não mostra nenhum seletor a menos que você passe um: localeSwitch recebe uma entrada por idioma — { code, label, dir, href, current, untranslated } — apontando para a página que você construiu para cada idioma.

Os links de descoberta para agentes no head também vêm junto: o layout lê a configuração resolvida, então uma página personalizada carrega os mesmos links describedby, ai-catalog e ard que as páginas de documentação, sem que você precise passar nenhuma prop. A página inicial também anuncia seu espelho em Markdown /index.md como alternativa text/markdown, já que esse espelho sempre existe; as outras páginas personalizadas não têm um, então nenhum é anunciado. Passe discovery={null} para remover os links de uma página específica.

Passe transparentHeader para que o cabeçalho comece transparente, com seus elementos em branco, para que ele possa ficar sobre um hero escuro no topo da página; ele vira a barra fosca de sempre assim que a página rola, e o diálogo de busca mantém as cores da própria página o tempo todo. O hero precisa passar por baixo do cabeçalho para que isso apareça — puxe-o para cima pela altura do cabeçalho (-mt-16) e adicione um espaçamento no topo para compensar.

Passar siteUrl (e ogEnabled) deriva automaticamente o canonical da página e uma og:image gerada: o Blume renderiza um card Open Graph para cada página personalizada estática (não para uma dinâmica [param]) — incluindo a home, a URL mais compartilhada — servido em /og/<route>.png (/og/index.png para /). O card da home traz o título do site como título principal, com a descrição do site como subtítulo logo abaixo; uma página mais profunda é intitulada a partir do último segmento do seu caminho. Defina ogImage ou canonical explicitamente para substituir qualquer um deles. ogImage aceita um caminho relativo à raiz — um arquivo em public/, resolvido em relação a deployment.site para a URL absoluta de que os rastreadores precisam — ou uma URL externa, que passa intacta:

<PageLayout
  siteUrl={config.site}
  ogEnabled={config.og.enabled}
  ogImage="/opengraph-image.png"
  ogImageAlt="Acme — the fastest docs"
  ogImageSize={{ width: 1200, height: 630 }}
  page={{ title: config.title }}
>
  <!-- page content -->
</PageLayout>

Apenas esta página muda — todas as outras rotas mantêm seu card gerado — então é assim que você dá somente à página inicial uma imagem de compartilhamento sob medida. O card gerado declara seu tamanho e texto alternativo aos rastreadores por conta própria; para a sua própria ogImage, passe ogImageAlt e ogImageSize junto para que o card de compartilhamento receba o mesmo tratamento.

A página também emite JSON-LD do schema.org — o mesmo grafo que as páginas de documentação carregam, com um nó WebPage na página inicial — para que a página inicial (geralmente uma página personalizada) não seja a única URL sem dados estruturados. Uma página personalizada não tem um arquivo de conteúdo de onde tirar uma data, então passe lastModified (uma string de data ou um Date) para emitir o dateModified dela; sem uma data legível por máquina, os mecanismos de busca podem tomar qualquer outra data exibida na página como a data da própria página. Passe structuredDataEnabled={config.structuredData} para mantê-lo em sincronia com a configuração structuredData, ou structuredDataEnabled={false} para desativá-lo em uma página específica.

page.title é usado literalmente como título do documento (sem o sufixo - siteTitle), já que páginas de marketing geralmente definem o seu próprio. Para dar a uma página personalizada toda a estrutura da documentação — barra lateral, índice e tudo mais — envolva-a no RootLayout, o layout que as páginas geradas usam. Puxe as props necessárias diretamente do blume:data:

---
import RootLayout from "blume/components/layout/RootLayout.astro";
import data from "blume:data";
---

<RootLayout
  site={{ title: data.config.title, description: data.config.description }}
  logo={data.config.logo}
  banner={data.config.banner}
  navigation={data.navigation}
  page={{ title: "Pricing", route: "/pricing" }}
  headings={[]}
  themeMode={data.config.theme.mode}
  searchEnabled={data.config.search.enabled}
  indexable={true}
>
  <h1>Pricing</h1>
</RootLayout>

Página 404

O Blume traz uma página não encontrada padrão de fábrica: uma mensagem “404” centralizada envolvida na estrutura do site (cabeçalho, busca, tema), servida para qualquer URL sem correspondência. O blume build a grava em 404.html, que hospedagens estáticas servem automaticamente, e o blume dev a exibe para rotas desconhecidas. Abaixo da mensagem, uma lista Onde procurar em seguida aponta para cada seção de nível superior, além dos índices sitemap.xml e llms.txt quando existirem, para que um leitor — ou um agente que seguiu uma URL desatualizada — tenha um caminho de volta.

A página também tem uma gêmea em Markdown em /404.md e uma gêmea em JSON em /404.json (detalhes de problema conforme a RFC 9457) com os mesmos links de recuperação (URLs absolutas assim que deployment.site estiver definido), além da descrição openapi.json quando a API JSON estiver ativa. Em um build de servidor na Vercel ou na Cloudflare, uma requisição para uma página inexistente que envie Accept: text/markdown recebe o corpo em Markdown com o status 404 em vez da estrutura HTML, e uma que envie Accept: application/json recebe o documento de problema — assim um agente nunca precisa analisar uma página cheia de interface para descobrir para onde ir em seguida. Na Vercel, o mesmo vale para uma URL .md ou .json sem arquivo correspondente.

Para substituí-la pela sua própria, adicione um pages/404.astro. Ela assume a rota /404 da mesma forma que pages/changelog.astro assume o changelog — sua página prevalece e a padrão é descartada, junto com suas gêmeas /404.md e /404.json. Construa-a como qualquer outra página personalizada, em PageLayout ou RootLayout:

---
import PageLayout from "blume/components/layout/PageLayout.astro";
import data from "blume:data";
---

<PageLayout
  site={{ title: data.config.title, description: data.config.description }}
  logo={data.config.logo}
  navigation={data.navigation}
  themeMode={data.config.theme.mode}
  searchEnabled={data.config.search.enabled}
  page={{ title: "Page not found", route: "/404" }}
  noindex={true}
>
  <section class="mx-auto max-w-2xl px-6 py-24 text-center">
    <h1>This page took a wrong turn</h1>
    <a href="/">Back to home</a>
  </section>
</PageLayout>

Para manter o design padrão mas alterar o texto — inclusive para outros idiomas — sobrescreva os textos da interface de notFound (title, description, home) via i18n.ui.

Páginas interativas

Páginas personalizadas são Astro comum, então você pode inserir ilhas React (ou de qualquer framework) com uma diretiva de hidratação. O React é ativado automaticamente assim que seu projeto contém um arquivo .tsx ou .jsx.

Última atualização a

Esta página foi útil?