Troubleshooting
The failures below account for nearly every "it doesn't work" report. They are all embedding issues, not bugs in the editor.
The editor renders blank / has zero height
Symptom. The constructor runs without throwing, but the container shows nothing — or a thin sliver, or just the toolbar with no canvas.
Cause. Apollon renders onto a React Flow canvas that sizes itself to its parent. If the container's height does not resolve to a positive pixel value, the canvas collapses to zero.
A percentage height (height: 100%) only resolves if every ancestor up to a
sized element also has a resolvable height. A bare <div> in document flow
has no height of its own.
Fix. Give the container an explicit height:
<div id="apollon" style="width: 100%; height: 600px"></div>
Acceptable heights: a fixed value (600px), a viewport unit (80vh), or a
percentage only when the parent chain is sized (a flex/grid child with
flex: 1, or height: 100% all the way up to a sized root). Width almost
never causes this — only height does.
If the container is sized correctly but the editor still looks empty, call
editor.fitView() after mounting: a model loaded into an off-screen viewport
can render outside the visible area.
The editor crashes during SSR / "window is not defined"
Symptom. ReferenceError: window is not defined (or document is not defined) at build time or on the server, in Next.js, Remix, Nuxt, SvelteKit,
or Angular Universal.
Cause. ApollonEditor is client-only. It calls ReactDOM.createRoot and
touches window / document during construction. None of that can run in a
server render pass.
Fix. Construct the editor only after the component has mounted on the client — never during render.
-
React / Next.js. Build inside
useEffect, and mark the file"use client". To keep the module out of the server bundle entirely, load the wrapper withnext/dynamicandssr: false:import dynamic from "next/dynamic"const DiagramEditor = dynamic(() => import("./DiagramEditor"), {ssr: false,}) -
Angular (17.3+). Construct inside
afterNextRender(() => …)— the callback is a no-op on the server, so the snippet is SSR-safe by construction. Older codebases on Angular 16 or earlier should guard withisPlatformBrowser(this.platformId)insidengAfterViewInit. -
Svelte / Vue / vanilla. Construct inside
onMount/mounted/ a client-side script — anything that runs only in the browser.
The editor mounts unstyled
Symptom. The editor appears, but with no theme — unstyled controls, misaligned elements, missing icons.
Cause. The stylesheet was never imported. Apollon ships its CSS as a separate file; importing the JS does not pull it in.
Fix. Import the stylesheet once, anywhere in your app:
import "@tumaet/apollon/style.css"
For a CDN / plain-HTML host, link it instead — see Vanilla JS / CDN.
Two copies of React (/react subpath vs. default build)
Symptom. In a React host: hook errors ("Invalid hook call", "more than one copy of React"), broken context, or a bundle that is larger than expected.
Cause. The default @tumaet/apollon build bundles its own copy of
React, MUI, emotion, and xyflow. That is correct for non-React hosts, but in a
React app it ships a second React that fights the host's.
Fix. In a React host, import from the /react subpath and install the
peer dependencies:
import { ApollonEditor } from "@tumaet/apollon/react"
The /react build externalizes React and friends so the editor shares the
host's single instance. Non-React hosts (Angular, Vue, Svelte, vanilla) should
keep the default @tumaet/apollon import. See
Install for the peer-dependency list.
Re-mounting leaks or double-renders
Symptom. After navigating away and back, the editor renders twice, stale subscriptions keep firing, or memory grows.
Cause. A previous ApollonEditor instance was never torn down. The editor
owns a React root, a Yjs document, and active subscriptions.
Fix. Always call editor.destroy() before discarding the instance or
mounting a new one on the same container. In a React useEffect, do this in
the cleanup function. destroy() unmounts the React tree, stops sync, drops
every subscription, and destroys the Yjs doc.
editor.getNodes() / getViewport() returns empty or null
Symptom. Calling geometry methods right after new ApollonEditor(...)
returns [] or null.
Cause. React Flow initializes asynchronously. Immediately after the constructor the instance is not yet wired up.
Fix. These methods are safe to call once the editor is interactive — in a
subscription callback, an event handler, or after a requestAnimationFrame.
getNodes() and getEdges() return [] and getViewport() returns null
until then; fitView() already retries internally and is safe to call right
away.