Server rendering

Both packages are safe to import on the server: nothing touches window or document until you use the editor in the browser. This is checked on every release by importing both packages in plain Node.

Next.js

@image-ultra/react is marked 'use client', so Server Components can render <ImageEditor> directly. The editor's frame (bars, tool rail) is rendered on the server; the photo appears once JavaScript runs.

Server Components can't pass functions (like onSave) to Client Components. Put the editor and its handlers in your own client component:

'use client';

import { ImageEditor } from '@image-ultra/react';

export function PhotoEditor({ src }: { src: string }) {
  return <ImageEditor src={src} onSave={(result) => console.log(result.state)} />;
}

React Router, Remix and others

The editor renders on the server like any component. No ClientOnly wrapper or dynamic import needed.

What only runs in the browser

Loading, rendering and exporting need a canvas: renderImage, exportImage, loadImage and the editor itself. Call them from event handlers or effects. Pure functions — createEditState, parseEditState and the types — work anywhere, including API routes that validate saved edits. On the server, import them from @image-ultra/core (npm install @image-ultra/core): in Next.js, server code that imports from a 'use client' package gets client references, not the functions.

import { EditStateError, parseEditState } from '@image-ultra/core';

export function validateEdits(body: unknown) {
  try {
    return parseEditState(body);
  } catch (error) {
    if (error instanceof EditStateError) return null;
    throw error;
  }
}