Performance Optimization
Follow these best practices to ensure optimal performance.
Bundle Size Optimization
Use Selective Imports
Treege exposes separate entry points. Import only what you use:
// ✅ Good — only the renderer (~62 KB vs ~117 KB gzip, no ReactFlow)
import { TreegeRenderer } from "treege/renderer"
// ✅ Good — only the editor
import { TreegeEditor } from "treege/editor"
// ✅ Good — need both, so import everything
import { TreegeEditor, TreegeRenderer } from "treege"
// ❌ Bad — only using the renderer, but pulling in everything (incl. the editor)
import { TreegeRenderer } from "treege"Subpath entries guarantee the unused half is excluded — don't rely on tree-shaking from the combined treege entry. (treege/editor alone saves little: it already bundles the shared code.)
Code Splitting
Lazy-load the editor so its bundle is only fetched when needed. The editor is client-only (it relies on ReactFlow and browser APIs), so the important part is keeping it out of server-side rendering.
Framework-agnostic (Vite, CRA, etc.) — React.lazy + Suspense:
import { lazy, Suspense } from "react"
const TreegeEditor = lazy(() =>
import("treege/editor").then((mod) => ({ default: mod.TreegeEditor }))
)
function Editor() {
return (
<Suspense fallback={<div>Loading editor…</div>}>
<TreegeEditor flow={flow} onSave={handleSave} />
</Suspense>
)
}Next.js — prefer next/dynamic with ssr: false:
In Next.js, React.lazy cannot disable SSR on its own, and server-rendering a browser-only component throws. Use next/dynamic with { ssr: false } instead:
import dynamic from "next/dynamic"
const TreegeEditor = dynamic(
() => import("treege/editor").then((mod) => ({ default: mod.TreegeEditor })),
{ ssr: false }
)The same pattern applies to the renderer if you want to code-split it. If you hit SSR/hydration issues with browser-only APIs, load it client-side too ({ ssr: false } in Next.js, or render it inside a client-only boundary).
React Performance
Memoize Callbacks
import { useCallback, useState } from "react"
export default function MyForm({ flow }) {
const handleSubmit = useCallback(async (data) => {
await fetch("/api/submit", {
method: "POST",
body: JSON.stringify(data)
})
}, [])
const handleChange = useCallback((data) => {
setValues(data)
}, [])
return (
<TreegeRenderer
flow={flow}
onSubmit={handleSubmit}
onChange={handleChange}
/>
)
}Memoize Flow Data
import { useMemo } from "react"
export default function MyForm() {
const flow = useMemo(() => ({
nodes: [/* ... */],
edges: [/* ... */]
}), [])
return <TreegeRenderer flow={flow} onSubmit={handleSubmit} />
}Validation Performance
Choose the Right Validation Mode
// ✅ Good for large forms - validates on submit
<TreegeRenderer
flow={flow}
onSubmit={handleSubmit}
validationMode="onSubmit"
/>
// ⚠️ Can be slow - validates on every change
<TreegeRenderer
flow={flow}
onSubmit={handleSubmit}
validationMode="onChange"
/>Tip: Use validationMode="onSubmit" for forms with many fields.
Best Practices
- ✅ Use selective imports (
treege/rendererortreege/editor) - ✅ Memoize callbacks with
useCallback - ✅ Use
validationMode="onSubmit"for large forms - ✅ Keep validation functions simple
- ✅ Use code splitting for the editor
- ✅ Enable production builds with minification