POST://tiptap-editor-notes
带点橘子味的馒头的头像
带点橘子味的馒头

FRONTEND / DESKTOP DEV

返回文章列表

用 TipTap 做富文本编辑器的经验谈

1 分钟· 415 44 次阅读

后台编辑器选了 TipTap(基于 ProseMirror),这块踩了不少坑,整理成文。

为什么选 TipTap

  • 无头(headless),样式完全自己控制;
  • 扩展机制清晰,StarterKit 开箱即用;
  • React 绑定成熟,useEditor + EditorContent 几分钟就能跑起来。

接入图片上传

TipTap 官方有 Image 扩展,但默认只支持传入 URL。要支持本地上传,需要自己在工具栏按钮里做文件上传:

import Image from "@tiptap/extension-image";

const editor = useEditor({
  extensions: [StarterKit, Image],
});

async function uploadImage(file: File) {
  const fd = new FormData();
  fd.append("file", file);
  const res = await fetch("/api/admin/upload", { method: "POST", body: fd });
  const { url } = await res.json();
  editor.chain().focus().setImage({ src: url }).run();
}

TipTap 编辑器界面

上传接口负责:校验文件类型(png/jpeg/webp/gif/svg/avif)、限制 5MB、生成随机文件名防止冲突。

HTML 与 Markdown 的转换

编辑器产出 HTML,但博客内容想用 Markdown 存储(可版本管理、可迁移)。用 turndown 做单向转换:

import TurndownService from "turndown";

const td = new TurndownService({
  headingStyle: "atx",
  codeBlockStyle: "fenced",
});
const markdown = td.turndown(editor.getHTML());

存储 Markdown、渲染时再用 remark 转回 HTML,等于把编辑器当成"Markdown 的可视化外壳"。

几个值得注意的坑

  1. immediatelyRender: false:Next.js SSR 下必须设置,否则 hydration 报错;
  2. 图片路径:上传后返回相对路径(如 /uploads/202608/xxx.png),存库和渲染都一致,别存绝对地址;
  3. 占位符Placeholder 扩展的配置要在 StarterKit 之后,否则可能不生效。

小结

TipTap 的可定制性很强,代价是很多细节要自己接(上传、校验、转换)。但一旦跑通,后台编辑体验和前台展示能完全统一,值得投入。

相关文章