Skip to content

Performance

FlowDrop’s editor is a rich component with many dependencies. This guide covers strategies to minimize bundle impact and ensure smooth performance at scale.

Approximate gzip sizes by entry point:

Entry PointApprox. gzip sizeNotes
@flowdrop/flowdrop/core~10 KBTypes and utilities only — no heavy deps
@flowdrop/flowdrop/editor~180 KBIncludes @xyflow/svelte, Svelte runtime
@flowdrop/flowdrop/form~25 KBForm fields without CodeMirror
@flowdrop/flowdrop/form/code~350 KBIncludes CodeMirror and language packs
@flowdrop/flowdrop/form/markdown~300 KBIncludes CodeMirror markdown mode
@flowdrop/flowdrop/playground~200 KBEditor + session management
@flowdrop/flowdrop~400 KBFull bundle (avoid in production)

Tip: Use specific entry points rather than @flowdrop/flowdrop to tree-shake unused modules.

The editor bundle is large. Load it only when the user navigates to the editor page:

// Vanilla JS — dynamic import
async function mountEditor(container) {
const [{ mountFlowDropApp }, { createEndpointConfig }] = await Promise.all([
import('@flowdrop/flowdrop/editor'),
import('@flowdrop/flowdrop/core')
]);
await import('@flowdrop/flowdrop/styles');
return mountFlowDropApp({
container,
endpoints: createEndpointConfig('/api/flowdrop')
});
}
import React, { Suspense, lazy } from 'react';
const FlowDropEditor = lazy(() => import('./FlowDropEditor'));
export function EditorPage() {
return (
<Suspense fallback={<div>Loading editor...</div>}>
<FlowDropEditor />
</Suspense>
);
}

Where FlowDropEditor is a wrapper component that calls mountFlowDropApp in a useEffect.

If you use code or markdown fields but not on every page, load them on demand:

async function mountEditorWithCodeFields(container, endpoints) {
// Load form fields that require CodeMirror only when needed
const [{ mountFlowDropApp }, { createEndpointConfig }] = await Promise.all([
import('@flowdrop/flowdrop/editor'),
import('@flowdrop/flowdrop/core')
]);
// This registers code/markdown fields into the global registry
await import('@flowdrop/flowdrop/form/code');
await import('@flowdrop/flowdrop/form/markdown');
return mountFlowDropApp({ container, endpoints });
}

FlowDrop accesses window, document, and browser APIs — it cannot run on the server. Always guard your mount calls.

import { browser } from '$app/environment';
import { onMount } from 'svelte';
let app;
onMount(async () => {
if (!browser) return;
const { mountFlowDropApp } = await import('@flowdrop/flowdrop/editor');
app = mountFlowDropApp({ container: document.getElementById('editor'), ... });
return () => app?.destroy();
});
'use client';
import dynamic from 'next/dynamic';
const FlowDropEditor = dynamic(() => import('../components/FlowDropEditor'), { ssr: false });
<template>
<ClientOnly>
<FlowDropEditor />
</ClientOnly>
</template>

If you see Failed to resolve import errors during development, exclude FlowDrop from Vite’s pre-bundling:

vite.config.ts
export default defineConfig({
optimizeDeps: {
exclude: ['@flowdrop/flowdrop', '@xyflow/svelte']
}
});

See the Installation guide for more setup tips.

For workflows with many nodes (50+):

const { workflowActions } = app;
workflowActions.batchUpdate(() => {
// All changes inside are applied as a single reactive update
workflowActions.addNode(nodeA);
workflowActions.addNode(nodeB);
workflowActions.addEdge(edge);
});

Group related changes into a single undo step:

import { historyActions } from '@flowdrop/flowdrop/editor';
historyActions.startTransaction();
// ... multiple changes
historyActions.endTransaction();

Disable auto-save for programmatic updates

Section titled “Disable auto-save for programmatic updates”

When making many changes programmatically, temporarily disable auto-save:

mountFlowDropApp({
container,
endpoints,
features: {
autoSaveDraft: false, // disable localStorage auto-save
showToasts: false // disable toast notifications
}
});

See Mount API for the full features options.