Fabric.js 需要浏览器:它会创建 <canvas> 元素,还会读取 window。在 Fabric.js Next.js 项目里,只在客户端渲染编辑器,页面其余部分照常在服务端渲染。
在客户端渲染编辑器
把编辑器放在单独的文件里并标记 'use client',再用跳过 SSR 的动态导入加载它:
// app/editor/editor-loader.tsx
"use client";
import dynamic from "next/dynamic";
export const Editor = dynamic(() => import("./editor-client"), {
ssr: false,
loading: () => <p>Loading the editor…</p>,
});// app/editor/page.tsx
import { Editor } from "./editor-loader";
export default function EditorPage() {
return (
<main>
<h1>Floor plan</h1>
<Editor />
</main>
);
}ssr: false 只能在客户端组件里使用,所以加载文件也要写 'use client'。
客户端组件
在 editor-client.tsx 里,用 effect 创建画布并使用 hooks,做法和 React 指南完全一样:
// app/editor/editor-client.tsx
"use client";
import { useDocumentEngine } from "fabricjs-document-engine/react";
import { createLocalStorage } from "fabricjs-document-engine/storage";
import { useFabricCanvas } from "./use-fabric-canvas";
const storage = createLocalStorage({ prefix: "plans:" });
export default function EditorClient() {
const { elementRef, canvas } = useFabricCanvas(1200, 800);
const engine = useDocumentEngine(canvas, { storage, autosave: true });
return <canvas ref={elementRef} aria-label="Floor plan editor" />;
}fabricjs-document-engine/react 入口已经标记了 'use client',所以导入这些 hooks 不会影响服务端组件。
在服务端
文档相关的辅助函数在 Node 里无需画布也能运行。在路由处理函数里用它们,先检查上传的文档再存储:
// app/api/documents/[id]/route.ts
import { secureDocument, validateDocument } from "fabricjs-document-engine";
export async function PUT(request: Request) {
const document = secureDocument(await request.json());
if (validateDocument(document).length > 0) {
return new Response("Invalid document", { status: 422 });
}
// Store the document, checking the revision in the same query.
return Response.json({ revision: 1 });
}Fabric.js Next.js 常见错误
- "window is not defined":Fabric.js 在 SSR 时运行了。用
ssr: false加载编辑器。 - "useState only works in client components":在标记了
'use client'的文件里,从fabricjs-document-engine/react导入 hooks。
更多解决办法见问题排查。