Fabric.js needs a browser: it creates <canvas> elements and reads window. In Fabric.js Next.js apps, render the editor only on the client, and keep the rest of the page server-rendered.
Render the editor on the client
Put the editor in its own file marked 'use client', and load it with a dynamic import that skips 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 is only allowed inside a client component, which is why the loader file has 'use client' too.
The client component
Inside editor-client.tsx, create the canvas in an effect and use the hooks, exactly as in the React guide:
// 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" />;
}The fabricjs-document-engine/react entry is already marked 'use client', so importing the hooks never breaks a server component.
On the server
The document helpers work in Node without a canvas. Use them in a route handler to check uploads before you store them:
// 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 });
}Common Fabric.js Next.js errors
- "window is not defined": Fabric.js ran during SSR. Load the editor with
ssr: false. - "useState only works in client components": import hooks from
fabricjs-document-engine/reactinside a file marked'use client'.
More fixes are in troubleshooting.