toolgarden.xyz
中文
browser tool developmentExcalidrawNext.jsdiagram editorfrontend engineering

How to Embed Excalidraw in Next.js without Asset or SSR Problems

A focused Excalidraw wrapper for Next.js covering dynamic import, local asset paths, editor options, localization, fullscreen fallback, export, and persistence choices.

ToolGarden tools prioritize browser-local processing, so files and text do not need to be uploaded to a server.

Published July 22, 202610 min readBy ToolGarden

Excalidraw is attractive because one React component provides a complete hand-drawn editor. The two failures that appear most often in real Next.js deployments are not drawing bugs: the editor is evaluated during server rendering, or its fonts, locales, and data chunks resolve from the wrong asset path.

A robust wrapper therefore starts with client-only loading and deterministic asset hosting. After that, the application can decide which native actions to expose, how to localize the editor, whether scenes remain ephemeral or persist, and how fullscreen behaves when the native browser API is unavailable.

Choose Excalidraw when the visual language fits

Excalidraw excels at architecture sketches, rough flows, workshop notes, and diagrams that should feel informal rather than pixel-perfect. It includes selection, connectors, text, images, scene loading, and image export. That makes it a strong embedded editor when the product does not need a custom shape engine.

A hand-drawn style is also a product decision. If users need multi-page canvases, a heavily customized UI, precise design tooling, or deep domain-specific shapes, compare the editor APIs before committing to the visual result alone.

RequirementExcalidraw fit
Hand-drawn flows and architecture sketchesStrong
Scene import and image exportBuilt in
Simple local single-user boardStrong
Custom collaboration backendPossible, but separate architecture
Pixel-precise UI design editorUsually not the target

Keep the editor out of the server-rendering path

Use a client component and import @excalidraw/excalidraw/index.css from the route layout. In an effect, configure the asset base and then dynamically import the package. This avoids evaluating DOM-dependent code on the server and keeps the editor chunk off unrelated pages.

The asset path must be assigned before the runtime tries to resolve fonts, locales, or data chunks. A helper guarded by typeof window is safe to call at module initialization in the client bundle and again immediately before import.

const ASSET_PATH = '/excalidraw/';

function configureAssets() {
  if (typeof window !== 'undefined') {
    window.EXCALIDRAW_ASSET_PATH = ASSET_PATH;
  }
}

useEffect(() => {
  let active = true;
  configureAssets();

  import('@excalidraw/excalidraw').then(({ Excalidraw }) => {
    if (active) setEditor(() => Excalidraw);
  });

  return () => { active = false; };
}, []);

Copy and version the Excalidraw assets

EXCALIDRAW_ASSET_PATH should point to a directory served by the same deployment, such as /excalidraw/. Copy the version-matched package assets into public/excalidraw and keep the trailing slash. Test a clean production build because development resolution can hide missing files.

Open the browser Network panel and filter for failed fonts, locale modules, and data chunks. Strict CSP deployments should allow the editor’s required workers and blobs only as narrowly as the chosen feature set requires.

  • Version package and copied assets together.
  • Do not depend on node_modules paths at runtime.
  • Verify Chinese and English locale chunks explicitly.
  • Exercise image insertion and export after deployment, not only basic rectangles.

Expose native UI actions instead of rebuilding them

UIOptions controls which native canvas actions and tools are visible. A general local board can enable clear, scene loading, save-to-file, image export, background changes, theme switching, and image insertion. This provides a complete workflow without duplicating modal, file, and serialization behavior in the product shell.

Use initialData for a predictable first canvas state, but do not confuse it with controlled state. It initializes the scene; continuous persistence should be built through change callbacks or Excalidraw’s imperative API.

<Excalidraw
  langCode={locale === 'zh' ? 'zh-CN' : 'en'}
  name="product-sketch"
  theme="light"
  initialData={{
    appState: {
      viewBackgroundColor: '#ffffff',
      currentItemStrokeColor: '#111827',
    },
  }}
  UIOptions={{
    canvasActions: {
      clearCanvas: true,
      loadScene: true,
      saveAsImage: true,
      toggleTheme: true,
    },
    tools: { image: true },
  }}
/>

Localize at the editor boundary

Map the application locale to Excalidraw’s langCode in one place. For example, zh becomes zh-CN while English uses en. The outer page title, privacy note, loading text, retry action, and fullscreen labels remain in the application’s message catalog.

This split keeps editor-owned strings aligned with the package translation while product-owned strings follow the website’s i18n workflow. Test the longest labels because the editor toolbar can wrap differently across locales.

Decide whether scenes are ephemeral, local, or synced

A minimal wrapper can rely on Excalidraw’s native load and save actions without retaining a copy in React state. That is a good privacy-first default for a utility page: the application does not collect the scene, and users explicitly save files when they want portability.

If automatic local restore is required, serialize elements, app state, and files carefully through onChange and store them in browser storage with debouncing and quota handling. If account sync or collaboration is required, file blobs, document permissions, encryption, migrations, and conflict handling become backend concerns.

  • Do not write to storage on every pointer movement without debounce.
  • Persist referenced files as well as scene elements or images will disappear.
  • Strip transient app-state fields before long-term storage.
  • Show users whether the board is unsaved, local-only, or synced.

Make fullscreen a product feature, not a CSS accident

The editor needs a container with an explicit height in embedded mode. For fullscreen, request native fullscreen on the wrapper and fall back to a fixed viewport container when the request rejects. Lock body scrolling in the fallback and release it during cleanup.

After entering or leaving either mode, dispatch resize so Excalidraw recalculates its viewport. Use min-h-0 on flex ancestors; without it, the canvas can overflow instead of shrinking inside a fullscreen column.

Handle loading and teardown deliberately

A blank white rectangle during a slow dynamic import looks broken. Render a neutral loading surface, switch to a localized error with retry if import fails, and cancel state updates after unmount. If the wrapper registers fullscreen or keyboard listeners, remove every listener and restore body styles in effect cleanup.

Keep analytics outside scene callbacks unless there is a clear, privacy-reviewed need. Tool usage can usually be measured with page and button events without collecting drawing elements, text, filenames, or embedded images.

Key takeaways

The reliable Excalidraw recipe is small but strict: client component, asset path before dynamic import, version-matched public assets, native UI composition, explicit persistence semantics, and a container that survives fullscreen changes. Most integration bugs disappear when those boundaries are designed first.

Frequently asked questions

Q.Why does Excalidraw fail with window is not defined in Next.js?

The package is being evaluated in a server-rendering context. Keep the editor in a client component and dynamically import it after mount so DOM-dependent code only runs in the browser.

Q.What does EXCALIDRAW_ASSET_PATH control?

It tells the Excalidraw runtime where to load supporting assets such as fonts, locale chunks, and other packaged resources. Point it to a version-matched public directory and configure it before importing the editor.

Q.Does Excalidraw automatically save an embedded scene?

Do not assume that it does. Native scene save and load actions let users manage files, while automatic restore requires an explicit persistence design using change data, files, storage limits, and migrations.

Q.Why are inserted images missing after restoring a scene?

Scene elements can reference binary files stored separately. Persisting only elements and app state loses those file blobs. Store and restore the files collection as part of the same document.

Q.Should I use Excalidraw or tldraw?

Choose by product needs. Excalidraw is excellent for a recognizable hand-drawn diagram style and native scene workflows. tldraw offers a different general whiteboard model with pages and extensive editor composition. Prototype both with your required persistence, assets, and mobile interactions.