Agent Skills › haydenbleasel/blume

haydenbleasel/blume

GitHub

提供Remotion视频开发的最佳实践,涵盖项目初始化、动画设计(优先使用interpolate)、样式规范及媒体资源引用方法。

4 个 Skill 710

安装全部 Skills

npx skills add haydenbleasel/blume --all -g -y
更多选项

预览集合内 Skills

npx skills add haydenbleasel/blume --list

集合内 Skills (4)

提供Remotion视频开发的最佳实践,涵盖项目初始化、动画设计(优先使用interpolate)、样式规范及媒体资源引用方法。
Remotion代码编写 视频动画实现 Remotion项目搭建
packages/video/.agents/skills/remotion-best-practices/SKILL.md
npx skills add haydenbleasel/blume --skill remotion-best-practices -g -y
SKILL.md
Frontmatter
{
    "name": "remotion-best-practices",
    "metadata": {
        "tags": "remotion, video, react, animation, composition"
    },
    "description": "Best practices for Remotion - Video creation in React"
}

When to use

Use this skills whenever you are dealing with Remotion code to obtain the domain-specific knowledge.

New project setup

When in an empty folder or workspace with no existing Remotion project, scaffold one using:

npx create-video@latest --yes --blank --no-tailwind my-video

Replace my-video with a suitable project name.

Designing a video

Before designing visual scenes, layouts, promos, motion graphics, or text-heavy videos, load rules/video-layout.md for video-first layout and text sizing guidance.

Animate properties using useCurrentFrame() and interpolate(). Prefer interpolate() over spring() unless physics-based motion is explicitly needed. Use Easing.bezier() to customize timing, including jumpy or overshooting motion.

For animations that should be editable in Remotion Studio, keep the interpolate() call inline in the style prop and use individual CSS transform properties (scale, translate, rotate) instead of composing a transform string.

import { useCurrentFrame, Easing, interpolate, useVideoConfig } from "remotion";

export const FadeIn = () => {
  const frame = useCurrentFrame();
  const { fps } = useVideoConfig();

  const opacity = interpolate(frame, [0, 2 * fps], [0, 1], {
    extrapolateRight: "clamp",
    extrapolateLeft: "clamp",
    easing: Easing.bezier(0.16, 1, 0.3, 1),
  });

  return <div style={{ opacity }}>Hello World!</div>;
};

Prefer:

style={{
  scale: interpolate(frame, [0, 100], [0, 1]),
  translate: interpolate(frame, [0, 100], ["0px 0px", "100px 100px"]),
  rotate: interpolate(frame, [0, 100], ["20deg", "90deg"]),
}}

Over:

const scale = interpolate(frame, [0, 100], [0, 1]);

style={{
  transform: `scale(${scale})`,
}}

CSS transitions or animations are FORBIDDEN - they will not render correctly.
Tailwind animation class names are FORBIDDEN - they will not render correctly.

Place assets in the public/ folder at your project root.

Use staticFile() to reference files from the public/ folder.

Add images using the <Img> component:

import { Img, staticFile } from "remotion";

export const MyComposition = () => {
  return (
    <Img src={staticFile("logo.png")} style={{ width: 100, height: 100 }} />
  );
};

Add videos using the <Video> component from @remotion/media:

import { Video } from "@remotion/media";
import { staticFile } from "remotion";

export const MyComposition = () => {
  return <Video src={staticFile("video.mp4")} style={{ opacity: 0.5 }} />;
};

Add audio using the <Audio> component from @remotion/media:

import { Audio } from "@remotion/media";
import { staticFile } from "remotion";

export const MyComposition = () => {
  return <Audio src={staticFile("audio.mp3")} />;
};

Assets can be also referenced as remote URLs:

import { Video } from "@remotion/media";

export const MyComposition = () => {
  return <Video src="https://remotion.media/video.mp4" />;
};

To delay content wrap it in <Sequence> and use from. To limit the duration of an element, use durationInFrames of <Sequence>. <Sequence> by default is an absolute fill. For inline content, use layout="none".

import { Sequence } from "remotion";

export const Title = () => {
  const frame = useCurrentFrame();
  const { fps } = useVideoConfig();

  const opacity = interpolate(frame, [0, 2 * fps], [0, 1], {
    extrapolateRight: "clamp",
    extrapolateLeft: "clamp",
    easing: Easing.bezier(0.16, 1, 0.3, 1),
  });

  return <div style={{ opacity }}>Title</div>;
};

export const Subtitle = () => {
  return <div>Subtitle</div>;
};

const Main = () => {
  const { fps } = useVideoConfig();

  return (
    <AbsoluteFill>
      <Sequence>
        <Background />
      </Sequence>
      <Sequence from={1 * fps} durationInFrames={2 * fps} layout="none">
        <Title />
      </Sequence>
      <Sequence from={2 * fps} durationInFrames={2 * fps} layout="none">
        <Subtitle />
      </Sequence>
    </AbsoluteFill>
  );
};

The width, height, fps, and duration of a video is defined in src/Root.tsx:

import { Composition } from "remotion";
import { MyComposition } from "./MyComposition";

export const RemotionRoot = () => {
  return (
    <Composition
      id="MyComposition"
      component={MyComposition}
      durationInFrames={100}
      fps={30}
      width={1080}
      height={1080}
    />
  );
};

Metadata can also be calculated dynamically:

import { Composition, CalculateMetadataFunction } from "remotion";
import { MyComposition, MyCompositionProps } from "./MyComposition";

const calculateMetadata: CalculateMetadataFunction<
  MyCompositionProps
> = async ({ props, abortSignal }) => {
  const data = await fetch(`https://api.example.com/video/${props.videoId}`, {
    signal: abortSignal,
  }).then((res) => res.json());

  return {
    durationInFrames: Math.ceil(data.duration * 30),
    props: {
      ...props,
      videoUrl: data.url,
    },
    width: 1080,
    height: 1080,
  };
};

export const RemotionRoot = () => {
  return (
    <Composition
      id="MyComposition"
      component={MyComposition}
      fps={30}
      width={1080}
      height={1080}
      defaultProps={{ videoId: "abc123" }}
      calculateMetadata={calculateMetadata}
    />
  );
};

Starting preview

Start the Remotion Studio to preview a video:

npx remotion studio

Optional: one-frame render check

You can render a single frame with the CLI to sanity-check layout, colors, or timing.
Skip it for trivial edits, pure refactors, or when you already have enough confidence from Studio or prior renders.

npx remotion still [composition-id] --scale=0.25 --frame=30

At 30 fps, --frame=30 is the one-second mark (--frame is zero-based).

Captions

When dealing with captions or subtitles, load the ./rules/subtitles.md file for more information.

Using FFmpeg

For some video operations, such as trimming videos or detecting silence, FFmpeg should be used. Load the ./rules/ffmpeg.md file for more information.

Silence detection

When needing to detect and trim silent segments from video or audio files, load the ./rules/silence-detection.md file.

Audio visualization

When needing to visualize audio (spectrum bars, waveforms, bass-reactive effects), load the ./rules/audio-visualization.md file for more information.

Sound effects

When needing to use sound effects, load the ./rules/sfx.md file for more information.

Visual and pixel effects

When creating a visual effect, prefer: 1. normal Remotion/HTML/CSS/SVG/filter/blend/mask animation, 2. a listed effect via rules/effects.md, including on HTML rendered through <HtmlInCanvas>, 3. a custom createEffect() via rules/effects.md when the user asks for a reusable/project-specific effect, 4. custom <HtmlInCanvas onPaint> via rules/html-in-canvas.md only if no effect fits.

For light leak overlays, see rules/light-leaks.md. Docs: https://www.remotion.dev/docs/effects

Available effects: brightness(), contrast(), colorKey(), duotone(), grayscale(), hue(), invert(), saturation(), tint(), thermalVision(), blur(), linearProgressiveBlur(), zoomBlur(), dropShadow(), glow(), lightTrail(), evolve(), mirror(), scale(), uvTranslate(), xyTranslate(), barrelDistortion(), chromaticAberration(), fisheye(), cornerPin(), wave(), burlap(), emboss(), dotGrid(), halftone(), noise(), noiseDisplacement(), pattern(), pixelate(), pixelDissolve(), scanlines(), speckle(), shine(), shrinkwrap(), vignette(), contourLines(), checkerboard(), halftoneLinearGradient(), gridlines(), whiteNoise(), tvSignalOff(), lines(), rings(), waves(), zigzag(), lightLeak(), starburst().

3D content

See rules/3d.md for 3D content in Remotion using Three.js and React Three Fiber.

Advanced audio

See rules/audio.md for advanced audio features like trimming, volume, speed, pitch.

Dynamic duration, dimensions and data

See rules/calculate-metadata.md for dynamically set composition duration, dimensions, and props.

Advanced compositions

See rules/compositions.md for how to define stills, folders, default props and for how to nest compositions.

Google Fonts

Is the recommended way to load fonts in Remotion. See rules/google-fonts.md for how to load Google Fonts.

Local fonts

See rules/local-fonts.md for how to load local fonts.

Getting audio duration

See rules/get-audio-duration.md for getting the duration of an audio file in seconds with Mediabunny.

Getting video dimensions

See rules/get-video-dimensions.md for getting the width and height of a video file with Mediabunny.

Getting video duration

See rules/get-video-duration.md for getting the duration of a video file in seconds with Mediabunny.

GIFs

See rules/gifs.md for how to display GIFs synchronized with Remotion's timeline.

Advanced Images

See rules/images.md for sizing and positioning images, dynamic image paths, and getting image dimensions.

Lottie animations

See rules/lottie.md for embedding Lottie animations in Remotion.

Measuring DOM nodes

See rules/measuring-dom-nodes.md for measuring DOM element dimensions in Remotion.

Measuring text

See rules/measuring-text.md for measuring text dimensions, fitting text to containers, and checking overflow.

Advanced sequencing

See rules/sequencing.md for more sequencing patterns - delay, trim, limit duration of items.

TailwindCSS

See rules/tailwind.md for using TailwindCSS in Remotion.

Text animations

See rules/text-animations.md for typography and text animation patterns.

Advanced timing

See rules/timing.md for advanced timing with interpolate and Bézier easing, and springs.

Transitions

See rules/transitions.md for scene transition patterns.

Transparent videos

See rules/transparent-videos.md for rendering out a video with transparency.

Trimming

See rules/trimming.md for trimming patterns - cutting the beginning or end of animations.

Advanced Videos

See rules/videos.md for advanced knowledge about embedding videos - trimming, volume, speed, looping, pitch.

Parameterized videos

See rules/parameters.md for making a composition parametrizable by adding a Zod schema.

Maps

For simple maps with little flyovers, consider using static map images. For complex maps with animated routes or flyovers, load the maps rule: rules/maplibre.md

Voiceover

See rules/voiceover.md for adding AI-generated voiceover to Remotion compositions using ElevenLabs TTS.

用于同步Blume文档与产品实际行为。通过审计近期PR、API和配置,修复事实性过时内容,验证构建并维护PR。仅在发现漂移时操作,否则报告无变更。
检查文档是否存在漂移 刷新过时的文档内容 执行定期文档审计 发布后保持文档最新
skills/blume-update-docs/SKILL.md
npx skills add haydenbleasel/blume --skill blume-update-docs -g -y
SKILL.md
Frontmatter
{
    "name": "blume-update-docs",
    "description": "Keep a Blume docs site in sync with the product it documents. Audit recently merged pull requests, changelogs, config schemas, CLI help, and public APIs against the docs content, update only pages that are factually stale, verify the docs build, and open (or update) a maintenance pull request — or report a clean no-op. Use when asked to check docs for drift, refresh stale documentation, run a scheduled docs audit, or keep docs current after a release."
}

Update Blume Docs

Blume is a markdown-first documentation framework on Astro/Vite: content lives as Markdown/MDX under a content root (default docs/), navigation derives from the file tree plus optional meta.ts files, blume build validates frontmatter and duplicate routes, and blume validate checks links and anchors.

Your job is docs maintenance, not docs authorship: find where shipped, user-facing behavior has drifted from what the docs claim, fix exactly that, prove the site still builds, and deliver the result as a pull request. A run that finds nothing actionable ends with a short report and no branch, no commit, no PR — prefer a no-op over a noisy PR.

Ground rules

  • Only document what shipped. Never invent features, timelines, pricing, APIs, or compatibility claims. Work behind a feature flag is not ready for docs unless the flag is enabled for the documented audience or the repo explicitly documents unreleased behavior.
  • Facts over polish. Edit when a command, option, default, route, prop, or workflow is wrong or missing. Skip subjective rewording, marketing polish, restructuring, and formatting-only churn.
  • Smallest correct diff. Touch the fewest pages that remove the drift. Preserve the site's voice, frontmatter style, component usage, and meta.ts navigation patterns.
  • Exact source-of-truth wording for commands, flags, config keys, environment variables, routes, and version numbers — copy them from code, don't paraphrase from memory.
  • Respect the repo. Follow AGENTS.md/CLAUDE.md conventions, don't touch generated output (.blume/, dist/), and never overwrite unrelated local changes.

Workflow

  1. Establish context.

    • Read the repo's agent/contributor instructions (AGENTS.md, CLAUDE.md, contribution docs) and honor them.
    • Locate the docs app and content root: blume.config.ts (content.root), the directory of .md/.mdx pages, meta.ts files, and the package manager + docs build command.
    • If this run was configured with a trigger, lookback window, docs path, target branch, or PR policy, honor those. Use the defaults below only where the prompt is silent.
  2. Reuse or create a maintenance branch.

    • If an open docs-maintenance PR from a previous run exists (head branch starting with blume/), check out and update that branch instead of opening a duplicate.
    • Otherwise branch from the default branch as blume/docs-refresh-YYYY-MM-DD. Create the branch only once you know an edit is needed.
  3. Find drift. Read references/audit-checklist.md for the full source list and change criteria, then:

    • Review PRs merged into the default branch within the lookback window (default: the last 7 days) and extract the user-facing changes.
    • Compare those changes — plus changelogs, release notes, config schemas, exported APIs, CLI help, and examples — against the docs content.
    • Check external links only when a checked page depends on them; prefer official docs and release notes over secondary sources.
    • Keep notes: what you checked, what changed upstream, and why each edit is (or isn't) needed.
  4. Update the docs.

    • Fix the stale pages. Add, rename, or remove meta.ts entries when pages are added, renamed, or deleted.
    • Match the surrounding pages: frontmatter shape, Blume components already in use, code-fence style, root-relative internal links.
  5. Verify.

    • Run the docs build (blume build or the repo's documented docs QA) — it validates frontmatter and duplicate routes.
    • Run blume validate to check internal links and anchors.
    • Run lint/format/typecheck when the repo's conventions call for them on docs changes.
    • Fix failures your edits caused; report pre-existing failures separately instead of fixing them in this PR.
  6. Deliver.

    • Changes made: commit only the maintenance edits, push the blume/* branch, and open or update a PR against the default branch titled like blume: refresh docs for YYYY-MM-DD. In the body list sources checked, docs changed, verification commands and results, skipped checks, and residual risk.
    • No changes needed: report the PRs and docs areas checked and the no-op result. Do not create a branch, commit, or PR.

Resources

  • references/audit-checklist.md — the source checklist, edit/skip criteria, and Blume-specific editing guidance. Read it before making docs changes.
Blume是基于Astro和Vite的Markdown优先文档框架。支持快速构建、本地搜索、AI集成(MCP/LLMs)及类型安全配置。适用于初始化项目、编写内容、调整配置或运行CLI命令的场景。
使用blume CLI进行项目初始化、开发或构建 编写或编辑Markdown/MDX文档内容 配置blume.config.ts或meta.ts文件 需要为文档站点添加AI功能如MCP服务器
skills/blume/SKILL.md
npx skills add haydenbleasel/blume --skill blume -g -y
SKILL.md
Frontmatter
{
    "name": "blume",
    "description": "Build and maintain documentation sites with Blume, the markdown-first docs framework on Astro and Vite. Use when working in a project that depends on `blume`, when scaffolding or configuring a docs site, writing Markdown\/MDX content, tuning navigation\/search\/theming\/SEO\/AI features, running the `blume` CLI (init, dev, build, eject), or editing `blume.config.ts` and `meta.ts` files."
}

Blume

Blume is an open-source, markdown-first documentation framework built on Astro and Vite. Drop Markdown or MDX into a folder, run blume dev, and get a production-grade docs site — navigation, search, theming, Open Graph images, and a rich component library — with no app boilerplate to write or maintain.

The core idea: the framework is the template. There's no starter to clone and no project to own before you've written a word. The only thing you touch is your content. When you outgrow the defaults, you add configuration one file at a time — and you can blume eject to a plain Astro project the day you want full control.

What makes it different

  • Fast by default — Static HTML on Astro/Vite. The core theme ships no client framework JS so pages score well on Core Web Vitals out of the box. You opt into server features only when you need them.
  • AI-ready out of the box — Emits llms.txt/llms-full.txt, serves any page's raw Markdown by appending .md to its URL, offers Copy as Markdown and Open in chat on every page, and can host an optional Ask AI assistant or an MCP server so coding agents read your docs directly.
  • Zero configuration — even the template — A folder of docs is a complete project. Navigation is inferred from files, search works in dev and production with no hosted service, and theming is a handful of tokens.
  • Type-safe to the coreblume.config.ts and every meta.ts are real TypeScript, validated by a schema and authored with defineConfig and defineMeta. Your editor autocompletes options and catches mistakes before a build.

Quickstart

Blume needs Node.js 22.12 or newer. From an empty or existing project:

npm i blume      # install the package
blume init       # scaffold: docs/index.mdx + blume.config.ts
blume dev        # dev server with hot reload
blume build      # static HTML to dist/, with a local search index

Blume works with any package manager and never requires you to set up Astro or Tailwind yourself.

Writing a page

Every page is Markdown or MDX with a little frontmatter. The title and description render as the page heading and intro automatically; built-in components (callouts, cards, tabs, steps, and more) need no imports.

---
title: Introduction
description: Welcome to my docs.
---

Welcome! Use **Markdown** and built-in components — no imports required:

:::note
Blume ships callouts, cards, tabs, steps, and more.
:::

Navigation, search, and page metadata are inferred from your files as you add them.

What's included

  • Components — callouts, cards, steps, tabs, accordions, badges, file trees, and parameter tables, usable in MDX with no imports.
  • Local search — Orama in dev and production; Pagefind is one flag away for large sites. No hosted index.
  • AIllms.txt, raw Markdown URLs, Copy as Markdown, Open in chat, an Ask AI assistant, and an MCP server endpoint served by the docs site itself.
  • Navigation — inferred from files, refined with meta.ts or config.
  • SEO — metadata, Open Graph images, RSS feeds, and JSON-LD.
  • Customization — component overrides, React islands, custom pages, theme tokens, and a source-component registry via blume add.
  • Ejectblume eject produces a standalone Astro project that still uses the blume package.

How it works

The Blume CLI discovers your content, builds a content graph, and generates a hidden Astro project under .blume/ that it drives for dev and build. The generated runtime is an implementation detail — you write Markdown, Blume handles the rest — until you choose to eject and own it.

Full documentation

This is a high-level overview. For complete, authoritative docs — configuration reference, every CLI command and flag, component APIs, content authoring, navigation, search, SEO, AI features, theming, and deployment — read the bundled docs in the installed package:

node_modules/blume/docs

Start with node_modules/blume/docs/index.mdx (Introduction) and node_modules/blume/docs/01-quickstart.mdx, then browse the configuration/, content/, reference/, and advanced/ sections for specifics.

将 Mintlify、Docusaurus 等文档站点迁移至 Blume。自动检测源框架,生成 blume.config.ts,重构导航,转换 JSX 为指令及图标。
用户要求迁移/转换/移植文档仓库到 Blume 检测到 docs.json、docusaurus.config.*、_meta.* 或 astro.config.* 等配置文件
skills/blume-migrate/SKILL.md
npx skills add haydenbleasel/blume --skill blume-migrate -g -y
SKILL.md
Frontmatter
{
    "name": "blume-migrate",
    "description": "Migrate an existing documentation site (Mintlify, Docusaurus, Fumadocs, Nextra, Starlight, or any docs framework) to Blume, the markdown-first docs framework on Astro. Translate the source config to blume.config.ts, restructure content into Blume's filesystem-derived navigation, rewrite JSX callouts to directives, convert icons to Lucide, and inline snippets. Use when the user asks to migrate\/convert\/port a docs repo to Blume, or when the repo has a docs.json\/mint.json, docusaurus.config.*, meta.json with fumadocs, _meta.* with nextra, or an astro.config.* with starlight()."
}

Migrate to Blume

Blume is a markdown-first documentation framework on Astro/Vite. You drop Markdown/MDX into a folder and get navigation, search, theming, Open Graph images, and a component library with no app boilerplate — the framework is the template. There is no starter to clone; the only thing a project owns is its content and a blume.config.ts.

Your job is to convert a source docs repo into an idiomatic Blume project — not a 1:1 transliteration. Read this file, detect the source framework, open the matching references/<framework>.md for the exact mappings, and work the loop below. Report everything you drop or approximate.

Migration philosophy

  • Target idiomatic Blume, not a mechanical port. Prefer filesystem-derived navigation over an exhaustive explicit navigation.sidebar. Prefer ::: directives over JSX callouts. Prefer Blume defaults over restating them in config.
  • Every field has a default; {} is a valid config. Map only what the source declares. If the source uses a framework default, don't write it.
  • Drop chrome that has no Blume equivalent — and say so. Navbar CTAs, footer columns, custom theming, dynamic redirects, and unmappable icons get reported to the user, not silently discarded or faked.
  • Convert, don't preserve. Blume's page frontmatter schema is strict — unknown keys are build errors. A source-only frontmatter key must be mapped to a Blume key or removed (and reported), never left to "maybe validate."

Migration workflow

  1. Detect the source framework and read its reference file:
    • docs.json / mint.jsonMintlify (references/mintlify.md) — the deepest, config-declared nav.
    • docusaurus.config.*Docusaurus (references/docusaurus.md).
    • meta.json + fumadocs-* deps (content under content/docs/) → Fumadocs (references/fumadocs.md).
    • _meta.{js,ts,json} + nextra deps → Nextra (references/nextra.md).
    • astro.config.* calling starlight({…})Starlight (references/starlight.md).
    • Anything else → apply this file's mental model directly; there's no framework-specific reference, so inventory by hand.
    • Also note the host repo, independent of source framework: a pnpm/Turbo workspace, a non-docs/ content layout, or a Vercel deploy each need integration steps (content.root scoping, minimumReleaseAge, lockfile, vercel.json, an Astro/Vite patch) — all in references/monorepo.md. Read it whenever the target isn't a bare single-package docs folder.
  2. Inventory the repo before changing anything: the config file(s), the content tree, the nav definition, snippets/partials/includes, static assets, OpenAPI/AsyncAPI specs, redirects, i18n locales, custom components, and icon usage. Note what's declared vs. defaulted.
  3. Write blume.config.ts with defineConfig from blume. Map only declared fields (see the reference's mapping table); rely on defaults everywhere else. A minimal result is defineConfig({ title: "…" }).
  4. Restructure content. Choose content.root (default docs) — detect where .md/.mdx actually live, don't assume a docs/ folder. Many repos keep content directly under an app dir (apps/docs/api/, .../getting-started/) with no docs/ subfolder; when so, set content.root to that dir and scope content.include to the real content folders rather than leaving a bare content.root: "." that scans everything (see references/monorepo.md §1). Order with numeric prefixes (01-intro.mdx), group without a URL segment via (group)/ folders, and add a meta.ts (defineMeta) only where filesystem order isn't enough. A source that already declares per-folder navigation in a sidecar file — Fumadocs meta.json, Nextra _meta.*is that case: convert each one to a meta.ts, carrying over its title/icon/order/collapse, rather than dropping it and hoping filenames reproduce the intent. Filesystem inference is the fallback for folders that declare no per-folder nav, never a reason to discard one that does. Reach for an explicit navigation.sidebar only when the source nav genuinely can't be expressed by files. Reshaping into folder-per-tab moves URLs — track every old→new path as you go; you'll turn them into redirects in step 5.
  5. Rewrite pages. Map frontmatter to Blume's strict schema; convert callout JSX to ::: directives — directives (and math/mermaid/package-install fences) are MDX-only, so rename any .md page that needs them to .mdx; rename components; inline snippets/partials (Blume has no import-based includes); fix asset paths; rewrite internal links to their new routes (including OpenAPI operation links — see the OpenAPI section, their slugs differ from most sources); add a redirects entry for every route you moved in step 4; convert every icon name to Lucide (Blume is Lucide-only — no FontAwesome/Tabler). Remove any duplicated H1 in the body (title renders the H1; bodies start at ##). If the source has a hand-maintained changelog and the repo is open source on GitHub, offer to swap it for the github-releases source (see "Changelogs" below) rather than porting the entries. For Mintlify, run the bundled codemod first — node <skill>/scripts/mintlify-codemod.mjs --write <content-dir> deterministically remaps icons and drops/renames unsupported frontmatter keys, and reports the rest (unknown icons, OpenAPI-stub flags) for you to finish by hand (see references/mintlify.md).
  6. Adopt package.json. Repoint dev/build/startblume dev/blume build/blume preview, remove the old framework's deps, add blume. A config-only source (e.g. a bare Mintlify docs.json) has no manifest — scaffold one. In a pnpm workspace: if pnpm-workspace.yaml/.npmrc sets minimumReleaseAge, add only blume to minimumReleaseAgeExclude (don't disable the guard) so the just-published version installs. Always regenerate the lockfile in the same change: after editing deps run a plain pnpm install (from the workspace root) and commit pnpm-lock.yaml alongside package.json — CI/Vercel use --frozen-lockfile, so a stale lockfile fails the build before it starts. If the repo uses (or the user wants) Ultracite for formatting: its oxfmt formatter mangles the ::: directives you just wrote unless you ship the bundled assets/oxfmt@0.55.0.patch and register it under patchedDependencies — see references/monorepo.md §6. See references/monorepo.md §2–3.
  7. Wire up the host repo & deploy (non-trivial repos). For a monorepo on Vercel, emit the root-aware install/build recipe and apps/docs/vercel.json, and tell the user the two settings you can't commit (Vercel Root Directory, Node 22). If the workspace pins Vite and blume build crashes inside Astro/Vite, apply the pnpm-patch workaround. All copy-pasteable in references/monorepo.md §4–5.
  8. Verify. Run blume build --strict (frontmatter schema, duplicate routes, config — without --strict a build exits 0 despite content errors, silently dropping invalid pages) and blume validate --strict (internal links, heading anchors, assets — the link checker lives in validate, not build), fix diagnostics, then blume dev for a visual pass. End with a written summary of what was migrated, dropped, and approximated — and every repo-specific edit you made (pnpm-workspace, vercel.json, config globs) with the reason, plus any manual step left to the user (the Astro patch, Vercel dashboard settings).

The Blume mental model

The single biggest shift for most sources — especially Mintlify — is that navigation is derived from the filesystem, not declared in config.

Navigation is the file tree

  • Folders become groups, files become pages. A page's sidebar label is its frontmatter title; a group's label is the humanized folder name.
  • Ordering resolves highest-priority-first: an explicit navigation.sidebar (replaces the whole tree) → a folder's meta.ts pages array → a page's frontmatter sidebar.order → the filesystem (index first, then numeric filename prefix like 01-, then alphabetical).
  • meta.ts refines one folder (defineMeta({ title, icon, order, collapsed, pages })). The pages array lists children by slug (numeric prefix and parentheses stripped); children you omit fall back to their own sidebar.order, then filesystem order (index still sorts first) — so a partial pages list is safe, but list every child when the source declared a complete order.
  • Sidebar render mode is global, not per-folder. navigation.sidebar.display in blume.config.ts is "flat" (default), "group" (collapsible), or "page" (drill-in sub-panel) and applies to every group at once. (It used to live on each folder's meta.ts as display; that field was removed — writing it in a meta.ts is now a build error. Set it once in config instead.) An explicit navigation.sidebar item may still override its own group's display.
  • An explicit navigation.sidebar replaces filesystem generation entirely. Use it only for a nav shape files can't express. Its items are a page route string, a group ({ label, items }), or a link ({ label, href }).
  • Config-declared nesting has no on-disk counterpart — materialize it or it flattens silently. When a source (Mintlify groups, Nextra _meta, a Docusaurus sidebar…) declares a nested group, its pages usually sit flat in one folder and the grouping lives only in config. Filesystem-derived nav sees the flat folder and drops the inner group. To keep the nesting you must either move those pages into a real subfolder (meta.ts for label/collapsed) — which changes their URLs, so add redirectsor declare the group in an explicit navigation.sidebar, which nests the existing routes without moving a file. Walk config pages/nav arrays recursively during inventory and record where config nesting depth exceeds on-disk depth; that gap is exactly what gets lost.

Tabs and selectors

  • navigation.tabs ({ label, path, icon? }) render top-of-header sections and scope the sidebar by route — the folder at a tab's path becomes the section, so this needs no config beyond the tabs themselves; structure content as one folder per tab. A source's top-level tabs (Mintlify navigation.tabs, a top-level product/section switcher) map to these header tabs — keep them as tabs; don't flatten them into a single global navigation.sidebar. Blume picks the active tab by URL prefix (longest tab path that prefixes the route), so every page in a tab must live under that tab's single path; a source tab that mixes arbitrary routes isn't portable as-is — either move its pages under one prefix (route change → add redirects) or accept the closest shape, and say which in the report (details in references/mintlify.md). The filtering runs both ways: on a route under a tab's path, the sidebar shows only that tab's folder (a tab also highlights when the current route is under it); on a root or untabbed route (or a tab whose path is /), the tab folders are hidden and the sidebar shows only the loose pages that belong to no tab (full tree as a fallback, so it's never blank). Consequence for migrations: once you add tabs, the landing sidebar automatically drops the sectioned content — that's intended, not lost pages; don't hand-build excludes for it.
  • navigation.selectors ({ kind, label, items: [{ label, path, icon?, description?, tag? }] }, kind = dropdown/product/version/language) partition a whole site (products, versions) via a header dropdown keyed on the current route.
  • navigation.featured ({ label, href, icon? }) pins links to the top of the sidebar, above every section — a blog, changelog, or support page that should always be one click away. These are the exception to tab scoping: unlike the generated tree, featured links show on every route and breakpoint. href points anywhere — an external URL opens in a new tab, an internal route (/contact) is validated against your pages at build time. icon is a Lucide name (or image path/URL/inline SVG), as everywhere else. This is the home for a source's always-visible header/utility links (Mintlify anchors, Blog/Contact links) — see references/mintlify.md.

Routes and pathing

  • A route is the content path relative to content.root, with numeric prefixes stripped (01-intro.mdx/intro) and (group)/ folders adding no segment. An index file maps to its folder's route. Frontmatter slug overrides the generated route.

blume.config.ts shape

defineConfig({...}) — every field optional, all with defaults:

  • Site: title, description, logo (string SVG, or { image: string | { light, dark, alt }, text, href }), banner ({ content, link, dismissible, id } — no color/type). A logo renders beside title in the header, so a wordmark logo doubles the brand ("Acme Acme") — set text: "" to render the mark alone. Prefer the string form over { light, dark }: if you have the logo SVG locally and it's monochrome (solid black or white), rewrite its fill/stroke to currentColor and use logo: "/logo.svg" — it then inherits the theme's text color and adapts to light/dark automatically, so you don't need separate light/dark files.
  • theme: accent (a color string for both modes, or { light, dark } per mode), action (color), mode (light/dark/system), radius, fonts ({ body, display, mono } — curated Google-font slugs), background and backgroundImage (each a string, or { light, dark } per mode). The old accentDark/backgroundDark/backgroundImageDark fields were merged into these per-mode objects — a bare string still applies to both modes, so only reach for { light, dark } when the two modes differ. There is no theme.strict and no theme.css config field — custom CSS goes in a project-root theme.css file (auto-picked-up), and a source's "strict appearance" flags drop.
  • content: root (default "docs", relative to the project dir where blume runs), include/exclude (arrays of globs relative to content.root; defaults ["**/*.{md,mdx}"] / ["**/_*", "**/.*"]), sources (staged sources: filesystem, github-releases, notion, sanity, mdx-remote, custom — OpenAPI is not one of these; it's the top-level openapi field), pages (custom .astro dir), defaultType. When docs sit directly under the project dir (no docs/ subfolder), set root there and scope include to the real content folders instead of scanning everything — references/monorepo.md §1.
  • basePath (top-level): a site-wide mount point (e.g. "/docs") prepended to every route while staying invisible to the sidebar (no wrapper group). This is the right target for a source that served all docs under a prefix (Docusaurus routeBasePath, a Fumadocs baseUrl of /docs) — distinct from a per-source prefix (which adds a nav group) and from deployment.base (host subdirectory).
  • navigation: tabs, selectors, featured (links pinned above the sidebar on every route), sidebar ({ display, items }display is the global render mode above; items is an explicit tree), repo. Avoid an explicit navigation.sidebar unless you have to — lean on the filesystem-derived sidebar. It only works when the file tree roughly matches the intended sidebar layout, so reshape folders to match first; reach for sidebar.items only for a shape files genuinely can't express (see "Config-declared nesting" above).
  • search (Orama default, Pagefind opt-in), ai (llms.txt, Ask AI, the MCP server), openapi, redirects, seo, markdown, analytics, deployment, i18n, toc, lastModified, github.
  • Don't set deployment.site. Blume auto-fills it: the dev server's localhost URL in dev, and the deployment URL (VERCEL_PROJECT_PRODUCTION_URL/VERCEL_URL) on Vercel. Hardcoding it in blume.config.ts overrides that auto-detection and pins the wrong absolute URL (canonical links, sitemap, OG, llms.txt) everywhere but the one host you typed — so leave it unset even when a source config had a url/site field. (Sitemap still generates in production because the deploy URL is present there.)
  • Favicon is a filename convention, not config. Drop icon/favicon.{svg,png,ico} (and apple-icon.png) in the project root or public/ — Blume auto-detects it. There is no favicon config field. A source favicon given as { light, dark } collapses to one — pick a single file and report the loss.

The schema is exported from blume/schema; the full field reference is in node_modules/blume/docs/configuration/.

Icons are Lucide, period

Blume resolves bare kebab-case Lucide names everywhere an icon is accepted — frontmatter icon, sidebar.icon, meta.ts icon, navigation.tabs/selectors icons, and Card/Step/Icon/etc. props. There is no FontAwesome or Tabler support and no iconType prop. Names must be kebab-case (book-open, not BookOpen) — a PascalCase React-component name (common in Fumadocs/lucide-react sources) does not resolve and renders nothing. When migrating a source that uses another icon set (Mintlify defaults to FontAwesome), map each name to its closest Lucide equivalent; where none exists, drop the icon and report it. Verify a name exists at lucide.dev/icons before writing it.

Page frontmatter (strict — unknown keys are build errors)

---
title: Install # renders as the page H1 — remove any duplicate H1 in the body
description: Install Blume and scaffold your first project.
type: doc # doc (default) | blog | changelog | api
icon: download # a Lucide name
sidebar:
  label: Install # overrides title in the sidebar
  order: 2
  icon: download
  badge: New
  hidden: false
seo:
  title: …
  description: …
  image: /og/install.png
  canonical: https://…
  noindex: false
search:
  exclude: false
  tags: [api]
slug: install # override the generated route
draft: false
lastModified: 2026-06-20 # pin the "last updated" date
---

Also valid: date/authors (blog/changelog feeds), changelog (changelog metadata), deprecated, hidden, noindex.

Authoring features (no imports needed in .mdx)

  • The rich features are MDX-only. Directives, package-install, mermaid, and math are wired into the MDX processor; in a plain .md file a :::note stays literal text — and the build stays green. Rename any .md file that uses (or should use) these to .mdx during migration. This bites hardest on Docusaurus/Starlight sources, whose .md content is full of ::: admonitions. Plain Markdown (headings, tables, fenced code with titles/highlighting) is fine in .md.
  • Callouts as directives: :::note, :::tip, :::warning, :::danger, :::info, :::success, with an optional title in brackets: :::warning[Heads up]. Aliases caution→warning, error→danger, important→note, warn→warning.
  • No-import MDX components: Callout, Card/CardGroup, Columns/Column, Steps/Step, Tabs/Tab, Accordion/AccordionItem, Expandable, FileTree, Tree/Tree.Folder/Tree.File, CodeGroup, Frame, Panel, Tooltip, Tile, Badge, Icon, TypeTable/AutoTypeTable, Color, YouTube, Visibility, GithubInfo, Component, CodeBlock, Diff, Prompt, Math. (Not shipped — convert away: <Warning> → the :::warning directive, and the ParamField/ResponseField/RequestField field family → TypeTable rows or the OpenAPI reference. See the reference files for targets.)
  • Fenced-code superpowers: ```package-install → package-manager tabs; ```mermaid → a rendered diagram; code-block titles (```ts server.ts), line numbers (lineNumbers), and highlighting ({1,4-5}, // [!code ++]).
  • Math: block math $$…$$ renders in .mdx with no config (there is no markdown.math field). Inline $…$ is not supported — a bare $ stays literal text; convert inline math to display math or drop it (report).

OpenAPI

openapi: { enabled: true, sources: [{ spec, label?, route? }] } generates one real page per operation — with routing, sidebar, search, and OG images for free. The reference does not get a header tab automatically — add a navigation.tabs entry pointing at the reference's route (reference routes are valid tab targets) or the API reference is unreachable from the header. Never hand-migrate generated API-reference pages (per-endpoint stub pages in the source): delete them and point openapi.sources at the spec. (renderer: "scalar" keeps the Scalar embed instead; AsyncAPI uses the same embed.)

  • Vendor the spec by default. A remote spec: URL makes every build depend on fetching it at build time — a single point of failure in CI, offline, or behind a proxy, and a failed fetch skips the whole reference. Prefer committing the spec into the repo (openapi/<name>.json) and pointing spec at the local path; if you keep the URL, say so and consider a prebuild step that refreshes the local copy with a fallback.
  • Operation routes have their own slug scheme<route>/<slugified-tag>/<slugified-operationId> (e.g. tag Models, id listModels/api-reference/models/listmodels). This rarely matches the source's endpoint links (Mintlify/others kebab-case differently), so rewrite every inbound link to an operation. blume validate resolves operation pages like any other route, so it catches the ones you miss.
  • Keep hand-written conceptual pages. Sources often pair a written "Introduction/Authentication" page with the endpoint group in the same tab. A normal content page placed under the openapi route merges into the reference tab's sidebar — so keep those (auth, errors, rate limits) and delete only the per-endpoint stubs.

Changelogs

If the source ships a hand-maintained changelog (a changelog.mdx, a folder of dated entries, Mintlify <Update> blocks) and the project is open source on GitHub, offer to replace it with the github-releases content source — release notes become the changelog automatically, with no files to maintain. It's an offer, not an automatic rewrite: some teams keep a curated changelog that doesn't map 1:1 to GitHub releases, so confirm the release notes are the source of truth before deleting their pages.

Add it under content.sources alongside the filesystem source:

content: {
  sources: [
    { include: ["docs/**/*.mdx"], root: ".", type: "filesystem" },
    {
      owner: "haydenbleasel",
      repo: "ultracite",
      prefix: "changelog",
      type: "github-releases",
    },
  ],
},
  • Each release materializes as a type: changelog page under /<prefix>/ (prefix: "changelog"/changelog/…); omit prefix to mount at the root.
  • Optional fields: limit (cap materialized releases, newest-first, default 100), prereleases (include prereleases), drafts (include drafts — needs a token with repo write access), pollInterval (dev polling seconds; omit to freeze for the session).
  • A private repo reads a token from GITHUB_TOKEN; it is never inlined in config. A public repo needs no token.
  • Delete the old changelog pages once the source is wired (and add redirects from their old routes to the new /<prefix>/… slugs). Pin a header/sidebar link with navigation.featured if the source had one.

Redirects are static

A redirects: [{ from, to, status? }] array in blume.config.ts maps old URLs when you restructure routes — Blume serves these itself, so any reorganization that moves a page (folder-per-tab, materialized nested groups, renamed slugs, index promotion) is fixed by adding an entry there; no host config needed. Restructuring is the main source of these: every page you moved in step 4 (folder-per-tab, renamed slugs, index promotion) needs an entry, or old URLs 404. status defaults to 301 (permanent — browsers cache it indefinitely); that's correct for genuine moves, but never use 301/308 for redirects you might reverse. Dynamic/wildcard patterns (:slug*) can't be modeled as static path-to-path; move those to host-level config (_redirects, vercel.json) and report them.

Verification & reporting

  1. Run blume build --strict — it validates the frontmatter schema, duplicate routes, and config, and --strict makes diagnostics fail the build (without it, blume build exits 0 despite content errors and silently drops invalid pages). Then run blume validate --strict — links, heading anchors, and assets live here, not in build (add --external to also check outbound HTTP links). OpenAPI operation pages are real routes to validate, so dead links to them are caught too. Iterate until both are clean.
  2. Run blume dev and review the site visually — nav structure, tabs, theme, rendered components.
  3. Write a migration summary covering: what was migrated (config, N pages, nav, OpenAPI), what was dropped (navbar CTAs, footers, custom theming, dynamic redirects, unmappable icons, unsupported components), and suggested follow-ups (blume eject for full control, blume add to vendor a component for customization).

Full documentation

The mapping details live in references/: one file per source framework (mintlify.md, docusaurus.md, fumadocs.md, nextra.md, starlight.md), plus monorepo.md for host-repo integration (content-layout detection, pnpm minimumReleaseAge, frozen-lockfile regeneration, the Vercel monorepo recipe, and the Astro/Vite patch). The Mintlify icon + frontmatter pass is automated by scripts/mintlify-codemod.mjs (zero-dependency, deterministic, idempotent; --write to apply). The authoritative Blume docs are bundled in the installed package at node_modules/blume/docs (or apps/docs/content/docs in a repo checkout). The most relevant pages:

  • configuration/index.mdx — every blume.config.ts field.
  • content/navigation.mdx — the sidebar/tabs/selectors model.
  • content/meta.mdxmeta.ts and display modes.
  • content/syntax.mdx — directives, code features, math.
  • content/components.mdx — the component library and APIs.
  • reference/frontmatter.mdx — the strict page schema.

首页 - Wiki
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-07-20 14:18
浙ICP备14020137号-1 $访客地图$