如何用 Streamdown 渲染 AI 流式 Markdown,并定制代码块(2026 版)
Sawana Huang, Claude Code - Mon Sep 22 2025
按当前官方资料重写的 Streamdown 教程,讲清楚它和 AI Elements Response 的关系、Tailwind 接入、流式 Markdown 渲染,以及如何通过 components 覆盖定制代码块。
这篇我收得比较狠。旧文信息不少,只是重心跑偏了:源码讲了很多,真正接入时最需要知道的事反而被压在后面。
实际落地时,最先会碰到的是这些问题:
- Streamdown 什么时候该用
- 它和 AI Elements
Response是什么关系 - 在 Next.js 里怎么接入
- Tailwind 样式为什么有时不生效
- 如果我只是想把代码块字体调大,应该从哪一层改
所以这版就不再拉着源码一段一段过,而是直接讲项目里怎么用。
先把关系说清楚
根据当前官方资料:
Streamdown是一个面向 AI 流式 Markdown 的react-markdown替代方案- AI Elements 的
Response组件就是基于它构建的 - 如果你已经在用 AI Elements,很多时候可以直接从
Response开始 - 如果你需要定制代码块、Mermaid、controls、主题等细节,那么直接用
Streamdown会更灵活
实际用的时候,可以这样分:
- 想快速渲染 AI 文本:先试
Response - 想深度定制 Markdown 渲染:下探到
Streamdown
Streamdown 适合什么场景
react-markdown 能处理“完整 Markdown”。
而 AI 场景里,你经常拿到的是:
- 正在流式生成的文本
- 尚未闭合的代码块
- 还没结束的粗体、链接、标题
这正是 Streamdown 要解决的问题。它的重点在于 Markdown 还没生成完时,页面也尽量稳稳地继续渲染。
它和 AI Elements Response 的关系
官方 README 已经写得很清楚:Streamdown 为 AI Elements 的 Response 组件提供底层能力。
而我们当前仓库里的 Response 包装层也很轻:
"use client";
import { cn } from "@/lib/utils";
import { type ComponentProps, memo } from "react";
import { Streamdown } from "streamdown";
type ResponseProps = ComponentProps<typeof Streamdown>;
export const Response = memo(({ className, ...props }: ResponseProps) => (
<Streamdown
className={cn(
"size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0",
className,
)}
{...props}
/>
));也就是说:
Response更像一个更贴 UI 规范的包装层Streamdown才是底层渲染引擎
完整接入步骤
2. 在 Tailwind 样式入口里加入 @source
这是最容易漏掉的一步。
根据官方 README,接入 Streamdown 后,需要让 Tailwind 扫描它的分发文件,否则一些样式类不会被收进最终产物。
在你的全局样式文件里加入:
@source "../node_modules/streamdown/dist/index.js";具体相对路径要按你的项目目录调整。重点是:路径必须能正确指到 node_modules/streamdown/dist/index.js。
3. 从最小可用示例开始
如果你只是想渲染一段流式 Markdown,最小示例就是:
"use client";
import { Streamdown } from "streamdown";
export function SimpleMessage({ text }: { text: string }) {
return <Streamdown>{text}</Streamdown>;
}这一步先解决“能渲染”,不要一开始就同时处理自定义 code block、Mermaid、复制按钮等问题。
4. 在 AI SDK 消息列表里使用它
官方 README 里给了一个很直接的方向:把消息里的文本 part 喂给 Streamdown。
例如:
"use client";
import { useChat } from "@ai-sdk/react";
import { Streamdown } from "streamdown";
export default function ChatMessages() {
const { messages } = useChat();
return (
<>
{messages.map((message) => (
<div key={message.id}>
{message.parts
.filter((part) => part.type === "text")
.map((part, index) => (
<Streamdown key={index}>{part.text}</Streamdown>
))}
</div>
))}
</>
);
}如果你已经在项目里有自己的 Response 包装层,也可以统一改用 Response 来维持样式一致性。
5. 先用 components 覆盖默认代码块,别一上来就 fork 整个库
这一步是本文真正的重点。
如果你的需求只是:
- 调大代码块字体
- 换复制按钮
- 自定义下载按钮
- 只改
code渲染
最划算的入口通常不是直接改 Streamdown 源码,先通过 components 覆盖它的 code 组件会轻很多。
我们当前仓库里的做法就是这样:
import { Streamdown } from "streamdown";
import { CustomCodeComponent } from "./custom-code-component";
<Streamdown
components={{
code: CustomCodeComponent,
}}
>
{markdownContent}
</Streamdown>;这个策略的优势非常直接:
- 改动范围小
- 可维护
- 仍然保留 Streamdown 其他默认能力
一个贴近本仓库的实际例子
当前仓库已经有一套针对代码块字体的定制 demo:
选择版本:
自定义版本 - 使用 text-sm 字体大小,提升可读性
选择演示内容:
这里是一个简单的工具函数:
这个函数提供了中文日期格式化功能。
原版 Streamdown
preClassName="...text-xs..."默认使用较小的字体,可能影响代码可读性
自定义版本
preClassName="...text-sm..."通过 components 参数替换 code 组件,提升字体大小
核心实现步骤:
- 基于官方 Streamdown CodeComponent,仅修改字体大小
- 将 preClassName 中的 text-xs 改为 text-sm
- 使用 React.memo 保持性能优化
- 通过 Streamdown 的 components prop 替换默认组件
- 保持所有原有功能:语法高亮、复制按钮、下载功能
这套实现的核心思想非常简单:
- 保留 Streamdown 的整体架构
- 只替换
components.code - 把代码块字体从默认更小的尺寸提高到更易读的尺寸
如果你的目标只是“让 AI 代码块更好读”,这是比整库 fork 更务实的路径。
什么时候该用 Response,什么时候该直接用 Streamdown
优先用 Response
适合这些情况:
- 你已经在用 AI Elements 风格的消息组件
- 你只是想统一渲染 AI 文本
- 你不想自己接管太多 Markdown 细节
直接用 Streamdown
适合这些情况:
- 你要覆盖
components - 你要控制
controls - 你要接 Mermaid 配置
- 你要自己定义容器样式和渲染策略
三个最常见的坑
1. 忘了 @source
这会让你以为“Streamdown 样式不全”或者“某些 class 没生效”,但真正的问题是 Tailwind 没扫到它的分发文件。
2. 需求只是改代码块字体,却一上来复制整套源码
除非你的改动已经深入到内部实现,否则优先尝试 components.code。
3. 把它当成普通 Markdown 渲染器来理解
它当然能渲染普通 Markdown,但它真正的价值在“流式、不完整、AI 输出”。
几个常用 props
根据官方 README,除了 children,平时最常碰到的是:
parseIncompleteMarkdown- 是否解析并样式化未闭合的 Markdown 片段
components- 自定义组件覆盖入口
shikiTheme- 代码高亮主题
mermaidConfig- Mermaid 配置
controls- 复制 / 下载按钮显示控制
真要继续深挖,也建议先从这几个入口下手。
总结
Streamdown 真正好用的地方,在于 AI 还没把 Markdown 说完的时候,页面也能继续正常工作。真要改样式或代码块,也别急着 fork 整个库,先试 components.code,通常就够用了。