---
title: Fontes de conteúdo
description: >-
  Traga documentação de arquivos locais, de um repositório remoto ou de qualquer backend personalizado — e combine várias fontes em um único site static-first lido em tempo de build.
---

Por padrão, o Blume lê uma pasta de arquivos `.md`/`.mdx`. As **fontes de conteúdo** permitem trazer páginas de outro lugar — um repositório remoto, um CMS ou qualquer backend personalizado — e combinar várias fontes em um único site. As fontes são lidas em tempo de build; o Blume continua static-first.

## O padrão [#the-default]

Sem nenhuma configuração, o Blume varre a raiz do seu conteúdo (`docs`, por padrão) como uma única fonte de sistema de arquivos implícita. As opções de nível superior `content.root`/`include`/`exclude` continuam funcionando exatamente como antes — nada a mudar.

```ts blume.config.ts
import { defineConfig } from "blume";

export default defineConfig({
  content: { root: "docs" },
});
```

## Múltiplas fontes [#multiple-sources]

Adicione um array `content.sources` para compor fontes. Cada entrada recebe um namespace por meio de um `prefix` opcional, de modo que suas rotas fiquem aninhadas sob `/<prefix>/…`. Quando `sources` está presente, ele substitui o padrão implícito, portanto inclua uma entrada `filesystem` para a sua documentação local.

```ts blume.config.ts
import { defineConfig } from "blume";

export default defineConfig({
  content: {
    sources: [
      // Local docs at the site root
      { type: "filesystem", root: "docs" },

      // Remote MDX from a GitHub repo, mounted under /sdk
      {
        type: "mdx-remote",
        prefix: "sdk",
        github: { owner: "acme", repo: "sdk", ref: "main", path: "docs" },
      },
    ],
  },
});
```

Se duas fontes resolverem para a mesma rota, o Blume reporta um erro de build `BLUME_DUPLICATE_ROUTE` — dê a cada fonte um `prefix` distinto.

## Obsidian

A fonte integrada `obsidian` lê um vault do [Obsidian](https://obsidian.md) no lugar onde ele está. Não há etapa de exportação nem nada gerado dentro do seu repositório: o vault continua sendo a fonte da verdade, e o Blume reduz o dialeto do Obsidian a Markdown conforme carrega.

```ts blume.config.ts
import { defineConfig } from "blume";

export default defineConfig({
  content: {
    sources: [
      { type: "filesystem", root: "docs" },
      {
        type: "obsidian",
        prefix: "notes",
        vault: "vault",
        // Vault folder names to skip at any depth, on top of dot-folders
        exclude: ["Templates", "Daily"],
      },
    ],
  },
});
```

Os `[[Wikilinks]]` viram links de rota, endereçados pelo nome da nota em todo o vault em vez de pelo caminho, do jeito que o Obsidian endereça as notas. Texto de link personalizado (`[[Note|label]]`), âncoras de cabeçalho (`[[Note#Install]]`), caminhos completos (`[[folder/Note]]` e `[[folder/Note.md]]`), os caminhos parciais que a configuração padrão "shortest path when possible" do Obsidian escreve (`[[guides/Note]]`) e a forma `[[Note\|label]]` que o Obsidian escreve dentro de uma célula de tabela — tudo funciona; e uma nota que define `slug` no frontmatter é linkada na rota que esse slug publica. Quando duas notas compartilham um nome, vence a nota cujo caminho completo no vault é exatamente esse nome — o Obsidian resolve um link como caminho antes de resolver como nome — e depois a primeira na ordem do vault (pastas antes de notas, sem diferenciar maiúsculas de minúsculas, como o explorador de arquivos do Obsidian). O Blume avisa apenas quando um wikilink de fato é resolvido por meio de uma colisão dessas; escreva um caminho mais longo para desambiguar. Uma referência de bloco (`[[Note#^id]]`) leva à nota dela sem âncora: os blocos são renderizados sem id em que aterrissar. Uma âncora de cabeçalho é resolvida contra os cabeçalhos reais da nota de destino, correspondidos do jeito que o autocomplete do Obsidian os escreve (com `**bold**`, `` `code` `` e sintaxe de link removidos) e convertidos em slug pela mesma passagem `extractHeadings` que preenche o manifesto da página — de modo que um link para `#Install` cai no cabeçalho, e não em um id que nenhuma página emite. `[[#Install]]` endereça um cabeçalho na própria nota que você está escrevendo. Um link para um cabeçalho que não existe mantém o link da página, descarta a âncora e emite um aviso.

O frontmatter mantém o que o [esquema de página](/docs/reference/frontmatter) do Blume aceita, mais qualquer chave que você declare em [`frontmatter.extend`](/docs/reference/frontmatter#custom-keys) (ou, para notas daquele `type`, o `frontmatter` de um tipo de conteúdo); toda outra propriedade do Obsidian — campos do Dataview, datas do Templater, `publish` e os próprios `tags`, `aliases` e `cssclasses` do Obsidian — é descartada quando uma nota é reduzida, então um vault escrito com a UI de Properties compila sem erros de frontmatter. `aliases` é descartado em vez de resolvido — alvos de link por alias ainda não são suportados. Uma imagem Markdown relativa ao lado de uma nota (`![chart](./chart.png)`) é servida a partir do vault e, quando o vault fica dentro do seu repositório git, as páginas do vault ganham [datas de "Última atualização"](/docs/configuration#last-modified) derivadas do git, como qualquer outra página. Os links de "Editar esta página" são resolvidos por meio de `github.dir`, então um vault que fica ao lado do app de documentação em um monorepo ainda linka para o arquivo dele; um vault fora do repositório não recebe link.

Diretórios de idioma e snapshots de versão dentro do vault são lidos do mesmo jeito que a fonte de sistema de arquivos os lê: `fr/Note.md` publica sob `/fr/` com [i18n](/docs/content/i18n) configurado, `v1.0/Note.md` sob `/v1.0/` com [versões](/docs/content/versioning), e wikilinks para essas notas apontam para a rota que cada uma publica.

:::note
Um cabeçalho que contém um link recebe sua âncora de manifesto a partir do Markdown do cabeçalho e seu `id` renderizado a partir do conteúdo de texto. Os dois divergem nesse cabeçalho, então um wikilink para ele pode cair na página em vez de na seção.
:::

Um link para uma nota `index` cai na rota da pasta dela, em vez de em um `/index` fantasma. **Um wikilink não resolvido degrada para texto simples com um aviso de build, em vez de quebrar o build**, então um vault no meio de uma refatoração ainda publica. `%%comments%%` de uma única linha são removidos, um wikilink dentro de um comentário HTML (`<!-- [[Draft]] -->`) é deixado em paz, já que o Obsidian também o esconde, e uma nota sem `title` no frontmatter é intitulada pelo nome do arquivo — a mesma regra que o próprio Obsidian aplica. Uma nota `index` é a única exceção: ela nomeia uma rota, e não uma nota, então o título dela recai na derivação usual do Blume (primeiro cabeçalho, depois o segmento humanizado). Código em blocos cercados, indentado e inline passa literalmente, então uma nota que documenta a sintaxe sobrevive.

Pastas que começam com ponto são ignoradas, incluindo o próprio diretório de configuração `.obsidian` do Obsidian e a `.trash` — que o watcher de desenvolvimento também ignora, então mover um painel no app ou mandar uma nota para a lixeira não refaz o build do seu site. Editar uma nota, sim. Os diretórios que nenhuma varredura de conteúdo lê (`node_modules`, `dist`, `.git`, …) também são ignorados, então um vault com raiz no próprio projeto não publica os READMEs das dependências. Symlinks dentro do vault são seguidos, do mesmo jeito que a fonte de sistema de arquivos os segue, então uma pasta compartilhada linkada no vault é publicada junto. Um vault que fica dentro de `content.root` precisa ser excluído da fonte de sistema de arquivos (`exclude: ["vault/**"]`); o `blume version cut` então o deixa de fora do snapshot, já que o vault continua publicando as próprias notas como atuais.

Ainda não reduzidos: callouts (`> [!note]`) são renderizados como blockquotes simples, embeds (`![[image.png]]`) passam intocados, `%%comments%%` de várias linhas são deixados no lugar e não há grafo de backlinks.

## MDX remoto [#remote-mdx]

A fonte integrada `mdx-remote` busca arquivos `.md`/`.mdx` brutos via HTTP. Enumere os arquivos a partir de uma subárvore de um repositório do GitHub (`github`) ou explicitamente contra uma URL base bruta (`url` + `files`):

```ts blume.config.ts
{
  type: "mdx-remote",
  prefix: "sdk",
  url: "https://raw.githubusercontent.com/acme/sdk/main/docs",
  files: ["intro.mdx", "guide.mdx"],
}
```

O token de um repositório privado é lido da variável de ambiente `GITHUB_TOKEN` — ele nunca é embutido na sua configuração ou na saída gerada, e é enviado somente para os próprios hosts do GitHub (`api.github.com`, `raw.githubusercontent.com`), nunca para uma base `url` personalizada.

As páginas remotas são renderizadas com fidelidade total de MDX-mais-componentes: seus corpos são materializados em um diretório de staging oculto e renderizados pelo Astro junto com a sua documentação local, de modo que callouts, abas e todos os demais componentes do Blume continuam funcionando.

### Cache e builds offline [#caching-and-offline-builds]

Cada fonte remota mantém um snapshot em `.blume/cache/<source>/`. Se uma busca falhar — uma instabilidade de rede ou uma indisponibilidade do CMS — o Blume serve o último snapshot válido conhecido com um aviso, em vez de quebrar o build. O cache fica dentro de `.blume/` e é regenerado, nunca versionado.

Em desenvolvimento, o conteúdo remoto é buscado uma vez e congelado durante a sessão; reinicie o servidor de desenvolvimento para atualizá-lo. As fontes locais do sistema de arquivos recarregam a quente normalmente. Para, em vez disso, consultar periodicamente uma fonte remota em busca de alterações, defina `pollInterval` (em segundos) nela — o servidor de desenvolvimento refaz a busca nesse intervalo e recarrega apenas quando o conteúdo realmente muda. Deixe-o sem definir para evitar chamadas à API enquanto você trabalha.

## GitHub Releases

A fonte integrada `github-releases` transforma as releases de um repositório em um changelog: cada release vira uma entrada `type: changelog`, de modo que suas notas de versão _são_ o seu changelog — nada a escrever duas vezes. Combinado com a [linha do tempo de changelog](/docs/advanced/changelog) gerada, publicar uma release no GitHub entrega uma entrada de changelog.

```ts blume.config.ts
import { defineConfig } from "blume";

export default defineConfig({
  content: {
    sources: [
      { type: "filesystem", root: "content" },
      {
        type: "github-releases",
        prefix: "changelog",
        owner: "acme",
        repo: "sdk",
        // prereleases: false,  // include prereleases (default off)
        // drafts: false,       // include drafts (needs a write token)
        // limit: 100,          // cap releases, newest-first
      },
    ],
  },
});
```

Cada release é mapeada automaticamente para os campos do changelog: seu nome (ou tag) vira o título, sua data de publicação define a ordem da linha do tempo, a tag vira `changelog.version`, e as prereleases são marcadas como `Prerelease` (as demais como `Release`). As notas são renderizadas como o corpo da entrada. Dê à fonte um `prefix` para que suas páginas de release fiquem aninhadas sob uma rota como `/changelog/v1-2-0`.

Um repositório privado se autentica com a variável de ambiente `GITHUB_TOKEN` — o mesmo token que os outros recursos do GitHub usam, nunca embutido na sua configuração. Como toda fonte remota, ela é armazenada em cache em `.blume/cache/<source>/` e servida offline se a API estiver inacessível. Como um changelog é complementar, uma falha de busca sem cache (digamos, um build de CI sem token) degrada para um changelog vazio com um aviso, em vez de quebrar o build — defina `GITHUB_TOKEN` nos seus ambientes de CI e de deploy para preenchê-lo.

## Sanity

A fonte integrada `sanity` executa uma consulta GROQ e mapeia os campos de cada documento para o frontmatter e seu corpo em Portable Text para Markdown. O pacote `@sanity/client` é uma dependência peer opcional — instale-o apenas se você usar esta fonte.

```ts blume.config.ts
import { defineConfig } from "blume";

export default defineConfig({
  content: {
    sources: [
      { type: "filesystem", root: "docs" },
      {
        type: "sanity",
        prefix: "guides",
        projectId: "abc123",
        dataset: "production",
        query: `*[_type == "guide"]`,
        // Field paths default to title / slug.current / body / _updatedAt
        fields: { slug: "slug.current", body: "content" },
      },
    ],
  },
});
```

Um token de leitura para um dataset privado vem da variável de ambiente `SANITY_TOKEN`. Tipos de bloco personalizados de Portable Text são mapeados para componentes do Blume por meio da opção `serializers` do adaptador, disponível quando você constrói `sanitySource` diretamente via uma [fonte personalizada](#custom-sources).

## Notion

A fonte integrada `notion` transforma um banco de dados do Notion em uma coleção: cada linha vira uma página, suas propriedades viram frontmatter e sua árvore de blocos vira MDX. Callouts, toggles, colunas e blocos de código são mapeados para os componentes correspondentes do Blume. Blocos de vídeo viram um embed `<YouTube>` quando contêm um link do YouTube e um player `<video>` caso contrário, com a legenda do bloco como legenda de `<Frame>` nos dois casos; um link para uma página de vídeo em vez de um arquivo de mídia (uma URL do Vimeo ou do Loom, digamos) é reportado como um aviso em vez de ser incorporado. O `@notionhq/client` (v5 ou posterior) é uma dependência peer opcional; o Blume lê o banco de dados por meio da primeira fonte de dados dele.

```ts blume.config.ts
import { defineConfig } from "blume";

export default defineConfig({
  content: {
    sources: [
      { type: "filesystem", root: "docs" },
      {
        type: "notion",
        prefix: "handbook",
        database: process.env.NOTION_DB_ID,
        // Property names default to the title-typed prop / Description / Slug / Order
        // Set publishedValue to treat Status as a publish gate (opt-in)
        publishedValue: "Published",
      },
    ],
  },
});
```

O token de integração vem da variável de ambiente `NOTION_TOKEN` (compartilhe o banco de dados com a sua integração). Por padrão, todas as páginas são importadas; defina `publishedValue` para fazer da propriedade `Status` um controle de publicação — qualquer outro valor passa então a ser mapeado para `draft: true`, que os builds de produção descartam. **As URLs de imagem e de vídeo do Notion são assinadas e expiram**, portanto o adaptador as baixa em tempo de build para os assets do site e reescreve as referências — um asset de CMS nunca apodrece um build estático. As chamadas à API são ritmadas por um pequeno pool de requisições (3 por vez, correspondendo ao limite de taxa por integração do Notion), de modo que bancos de dados com centenas de páginas são importados sem esbarrar em respostas `429`; defina `concurrency` na fonte para ajustar isso.

## Preview e sincronização [#preview-and-sync]

Duas flags controlam como o conteúdo remoto é buscado e o que é incluído:

- **`--preview`** em `blume dev` ou `blume build` renderiza rascunhos e traz conteúdo não publicado do CMS — o Sanity muda para sua perspectiva `previewDrafts`, e o Notion para de filtrar por `Status`. Builds de produção sem a flag excluem rascunhos normalmente, então um build de preview é uma forma segura de revisar trabalho não publicado antes de ele ir ao ar.
- **`blume sync`** refaz a busca de todas as fontes remotas e regenera o runtime. O ambiente de desenvolvimento é cache-first — uma fonte remota é buscada uma vez e servida a partir de `.blume/cache` ao reiniciar (rápido e tolerante a offline), portanto `blume sync` é como você traz o conteúdo mais recente do CMS sem reiniciar o servidor de desenvolvimento (um servidor em execução recarrega a quente). Adicione `--force` para descartar o cache antes, ou defina `pollInterval` em uma fonte para atualizar automaticamente.

```sh
blume dev --preview      # author workflow: see drafts live
blume build --preview    # render a full preview build
blume sync               # refresh remote content now
blume sync --force       # ...ignoring any cached snapshot
```

## Fontes personalizadas [#custom-sources]

Qualquer objeto que implemente a interface `ContentSource` pode ser passado diretamente, e é assim que um adaptador com serializers personalizados — ou qualquer backend não integrado — se conecta sem que seu SDK toque a instalação principal:

```ts blume.config.ts
import { defineConfig } from "blume";
import { sanitySource } from "blume/sources/sanity.ts";

export default defineConfig({
  content: {
    sources: [
      { type: "filesystem", root: "docs" },
      {
        type: "custom",
        source: sanitySource({
          name: "guides",
          prefix: "guides",
          projectId: "abc123",
          dataset: "production",
          query: `*[_type == "guide"]`,
          // Map custom Portable Text blocks to Blume components
          serializers: {
            callout: (block) => `<Callout>${block.text}</Callout>`,
          },
        }),
      },
    ],
  },
});
```

Uma fonte normaliza seu formato nativo (Portable Text, blocos do Notion, HTML remoto) para texto Markdown/MDX, de modo que os mesmos componentes e recursos de markdown se aplicam, não importa de onde uma página venha.

Uma fonte personalizada que lê arquivos locais deve definir `sourcePath` em cada entrada e `contentRoot` na própria fonte. O `sourcePath` nomeia o arquivo nos diagnósticos e resolve as imagens relativas ao lado dele; o `contentRoot` delimita o `log` do git que data as páginas, então sem ele as páginas da fonte não recebem [data de "Última atualização"](/docs/configuration#last-modified) derivada do git.
