Skip to content

Performance Optimization

This guide covers strategies for optimizing Vizel editor performance in production applications.

Bundle Size Optimization

Disable Unused Features

Vizel enables all features by default. Disable features you don't use to reduce bundle size and extension processing:

typescript
const editor = useVizelEditor({
  features: {
    mathematics: false,  // KaTeX (~80KB gzipped)
    diagram: false,      // Mermaid (~40KB gzipped, lazy-loaded)
    embed: false,        // oEmbed/OGP processing
    codeBlock: false,    // lowlight/highlight.js (~20KB gzipped)
    table: false,
    dragHandle: false,
  },
});

Feature Size Impact

The following table shows approximate sizes (minified + gzipped) of optional features:

FeatureApproximate SizeDependencies
mathematics~80KB gzippedKaTeX
diagram~40KB gzippedMermaid (lazy-loaded); GraphViz via @hpcc-js/wasm-graphviz adds additional size
codeBlock~20KB gzippedlowlight / highlight.js
embed~5KB gzippedBuilt-in oEmbed
table~8KB gzipped@tiptap/extension-table
dragHandle~3KB gzippedBuilt-in
textColor~2KB gzippedBuilt-in
taskList~2KB gzippedBuilt-in
details~2KB gzippedBuilt-in

Measurement

Actual bundle sizes depend on your bundler configuration and tree-shaking. You can use tools like bundlephobia or your bundler's analysis output to measure exact sizes:

bash
# Vite
npx vite-bundle-visualizer

# Webpack
npx webpack-bundle-analyzer

Lazy Loading

Vizel uses createLazyLoader() internally for optional dependencies like Mermaid. Vizel lazy-loads diagram rendering on first use, so simply having the feature enabled does not add to initial load time.

You can also lazy-load optional CSS stylesheets:

typescript
// Only import mathematics CSS when the feature is used
if (useMathematics) {
  import('@vizel/core/mathematics.css');
}

Code Block Language Optimization

By default, code blocks load all common languages. You can limit this to only the languages you need:

typescript
import { createLowlight } from 'lowlight';
import javascript from 'highlight.js/lib/languages/javascript';
import typescript from 'highlight.js/lib/languages/typescript';
import css from 'highlight.js/lib/languages/css';

const lowlight = createLowlight();
lowlight.register('javascript', javascript);
lowlight.register('typescript', typescript);
lowlight.register('css', css);

const editor = useVizelEditor({
  features: {
    codeBlock: { lowlight },
  },
});

Or use the common subset instead of all:

typescript
import { createLowlight, common } from 'lowlight';

// ~40 common languages instead of all ~190
const lowlight = createLowlight(common);

Large Document Handling

Character Count Limits

You can use the character count feature to enforce document size limits:

typescript
const editor = useVizelEditor({
  features: {
    characterCount: {
      limit: 50000, // Maximum characters
    },
  },
});

Optimize onUpdate Handlers

You should avoid expensive operations on every keystroke:

typescript
// Bad: Expensive serialization on every keystroke
onUpdate: ({ editor }) => {
  setContent(editor.getJSON());        // Triggers re-render
  localStorage.setItem('content',
    JSON.stringify(editor.getJSON())); // Synchronous I/O
},

// Good: Use auto-save with debouncing
const editor = useVizelEditor({
  onUpdate: ({ editor }) => {
    // Only lightweight operations here
  },
});

useVizelAutoSave(() => editor, {
  debounceMs: 2000,
  storage: 'localStorage',
  key: 'my-content',
});

Debounce State Updates

When syncing editor state to external state management:

tsx
import { useMemo, useState } from 'react';
import { useVizelEditor } from '@vizel/react';

function Editor() {
  const [content, setContent] = useState(null);

  // Debounce content updates
  const debouncedSetContent = useMemo(
    () => {
      let timer: ReturnType<typeof setTimeout>;
      return (json: unknown) => {
        clearTimeout(timer);
        timer = setTimeout(() => setContent(json), 300);
      };
    },
    []
  );

  const editor = useVizelEditor({
    onUpdate: ({ editor }) => {
      debouncedSetContent(editor.getJSON());
    },
  });

  return <VizelEditor editor={editor} />;
}
vue
<script setup lang="ts">
import { shallowRef } from 'vue';
import { useVizelEditor, VizelEditor } from '@vizel/vue';

const content = shallowRef(null);
let timer: ReturnType<typeof setTimeout>;

const editor = useVizelEditor({
  onUpdate: ({ editor }) => {
    clearTimeout(timer);
    timer = setTimeout(() => {
      content.value = editor.getJSON();
    }, 300);
  },
});
</script>

<template>
  <VizelEditor :editor="editor" />
</template>
svelte
<script lang="ts">
  import { createVizelEditor, VizelEditor } from '@vizel/svelte';

  let content = $state(null);
  let timer: ReturnType<typeof setTimeout>;

  const editor = createVizelEditor({
    onUpdate: ({ editor }) => {
      clearTimeout(timer);
      timer = setTimeout(() => {
        content = editor.getJSON();
      }, 300);
    },
  });
</script>

<VizelEditor editor={editor.current} />

Auto-Save Configuration

Debounce Timing

Choose a debounce value that balances responsiveness and performance:

Use CaseRecommended debounceMs
Real-time collaboration500ms
General editing1000ms (default)
Large documents2000–3000ms
Server-side persistence3000–5000ms

Storage Backend Selection

BackendBest ForConsiderations
localStorageQuick prototyping~5MB limit, synchronous
sessionStorageTemporary draftsCleared on tab close
Custom (API)Production appsAsync, no size limit

See Auto-Save for detailed configuration.


Framework-Specific Tips

React

  • Avoid creating a new onUpdate function on every render. Use useCallback or define the function outside the component.
  • Wrap toolbar components that receive the editor instance with React.memo
  • Do not store the entire editor state in React state. Use the useVizelEditorState hook for reactive state tracking.
tsx
import { useVizelEditor, useVizelEditorState } from '@vizel/react';

function Editor() {
  const editor = useVizelEditor({});

  // Reactive state without manual onUpdate tracking
  const { characterCount, wordCount } = useVizelEditorState(() => editor);

  return (
    <div>
      <VizelEditor editor={editor} />
      <span>{characterCount} characters, {wordCount} words</span>
    </div>
  );
}

Vue

  • Use shallowRef for the editor instance (useVizelEditor does this automatically)
  • Avoid watch with deep: true on the editor. Use the useVizelEditorState composable instead.
  • Use computed for derived values from editor state
vue
<script setup lang="ts">
import { useVizelEditor, useVizelEditorState } from '@vizel/vue';

const editor = useVizelEditor({});

// Reactive state tracking
const { characterCount, wordCount } = useVizelEditorState(() => editor.value);
</script>

Svelte

  • Svelte 5 runes handle reactivity efficiently by default
  • Use the createVizelEditorState rune for reactive state tracking
  • Avoid frequent $effect calls that read the editor state. Batch updates instead.
svelte
<script lang="ts">
  import { createVizelEditor, createVizelEditorState } from '@vizel/svelte';

  const editor = createVizelEditor({});

  // Reactive state tracking
  const editorState = createVizelEditorState(() => editor.current);
</script>

<span>{editorState.current.characterCount} characters</span>

Production Checklist

Before deploying to production, verify the following optimizations:

  • [ ] Disable unused features — Remove features your application does not use
  • [ ] Optimize code block languages — Register only the languages you need
  • [ ] Configure auto-save debouncing — Set an appropriate debounceMs for your use case
  • [ ] Debounce onUpdate handlers — Avoid expensive operations on every keystroke
  • [ ] Set character count limits — Prevent excessively large documents if applicable
  • [ ] Lazy load optional CSS — Import mathematics.css only if you use math features
  • [ ] Enable compression — Configure gzip or brotli compression on your server
  • [ ] Analyze bundle size — Use bundle analysis tools to identify optimization opportunities
  • [ ] Test with realistic content — Profile with documents similar to production usage

Next Steps

Released under the MIT License.