Skip to content

New Next.js Project Without UI

This guide walks you through using the CreativeEditor SDK (CE.SDK) Engine without its built-in UI in a Next.js application. This setup is ideal for building a completely custom editing interface or automating design workflows entirely through code—no visual editor required.

Who Is This Guide For?

This guide is for developers who:

  • Want to build a custom UI instead of using the default CE.SDK editor.
  • Need to integrate CE.SDK into client-side automation workflows without rendering a visual interface.
  • Have already completed a “Getting Started with CE.SDK in Next.js” tutorial and are ready to explore more advanced use cases.

What You’ll Achieve

  • Initialize the CE.SDK headless engine inside a client-side Next.js component.
  • Programmatically create and modify a scene, including a custom button that reduces the opacity of a sample image by 20% each time it’s clicked.
  • (Optional) Render the CE.SDK canvas for visual feedback—while keeping full control through your custom UI.

Prerequisites

Before you begin, make sure you have:

  • A working Next.js project.
  • Completed the “Getting Started with CE.SDK in Next.js” guide.
  • A valid CE.SDK license key (start a free trial to redeem yours).

Step 1: Install CE.SDK Engine

To use CE.SDK in headless mode, install the SDK via the @cesdk/engine npm package:

Terminal window
npm install @cesdk/engine

Step 2: Define a Custom Component for Your Headless CE.SDK Integration

Inside your Next.js components folder, create a new file named CustomEditor.js defining the following component:

'use client';
import CreativeEngine from '@cesdk/engine';
import { useEffect, useRef, useCallback } from 'react';
export default function CustomEditor() {
// reference to store the DOM container where the CreativeEngine canvas will be attached
const canvasRef = useRef(null);
// reference to store the CreativeEngine instance
const engineRef = useRef(null);
// reference to store the the ID of the image block added to the scene
const imageBlockIdRef = useRef(null);
useEffect(() => {
// your CE.SDK configurations
const config = {
license: '<YOUR_CE_SDK_LICENSE>', // replace it with your license key
};
// initialize CreativeEngine in headless mode
CreativeEngine.init(config).then(engine => {
// to avoid initializing CreativeEngine twice in strict mode
if (!engineRef.current) {
engineRef.current = engine;
// append CE.SDK canvas to the DOM
if (canvasRef.current) {
canvasRef.current.appendChild(engine.element);
}
// get the current scene or create a new one
let scene = engine.scene.get();
if (!scene) {
scene = engine.scene.create();
const page = engine.block.create('page');
engine.block.appendChild(scene, page);
}
// get the first page block
const [page] = engine.block.findByType('page');
// appen a block to show an image on the page
const imageBlockId = engine.block.create('graphic');
imageBlockIdRef.current = imageBlockId;
engine.block.setShape(imageBlockId, engine.block.createShape('rect'));
// fill the block with an image from a public source
const imageFill = engine.block.createFill('image');
engine.block.setSourceSet(imageFill, 'fill/image/sourceSet', [
{
uri: 'https://img.ly/static/ubq_samples/sample_1_1024x683.jpg',
width: 1024,
height: 683,
},
]);
engine.block.setFill(imageBlockId, imageFill);
engine.block.appendChild(page, imageBlockId);
// zoom to fit the page in the editor view
engine.scene.zoomToBlock(page);
}
});
}, []);
const changeOpacity = useCallback(() => {
const engine = engineRef.current;
const imageBlockId = imageBlockIdRef.current;
if (engine && imageBlockId != null) {
// get the current opacity value of the image
const currentOpacity = engine.block.getOpacity(imageBlockId);
// reduce the opacity image by 20% at each click
engine.block.setOpacity(imageBlockId, currentOpacity * 0.8);
}
}, [engineRef, imageBlockIdRef]);
return (
<div style={{ width: '100vw', height: '100vh', position: 'relative' }}>
<div ref={canvasRef} style={{ width: '100%', height: '100%' }} />
<div style={{ position: 'absolute', top: 20, left: 20 }}>
<button onClick={changeOpacity}>Reduce Opacity</button>
</div>
</div>
);
}

This component must include "use client" at the top because it uses React hooks and browser APIs.

Once the CreativeEngine is initialized, it allows you to programmatically create and manipulate the scene. In this example:

  • A sample image is added to the canvas.
  • A “Reduce Opacity” button adjusts the image’s opacity by 20% with each click by relying on the changeOpacity() callback.
  • The changeOpacity() function utilizes the CE.SDK block API to dynamically reduce the image’s opacity.

Note: Attaching the CE.SDK canvas to the DOM is completely optional. You can also run the engine purely in headless mode—for automation tasks like modifying graphics or generating exports—without rendering anything visually.

Step 3: Create a Client-Side Editor Component

CreativeEngine requires browser-specific features like document—and dynamic DOM manipulation, if you attach it to the canvas. Because of that, the CustomEditor must only run on the client. In other words, it should never be server-side rendered (SSR). To ensure that, you need to load it dynamically using Next.js’s dynamic import with ssr: false.

To avoid repeating this dynamic import in every file where you use the component, create a wrapper component named CustomEditorNoSSR.js inside your components folder:

'use client';
import dynamic from 'next/dynamic';
const CustomEditorNoSSR = dynamic(() => import('./CustomEditor'), {
ssr: false,
});
export default CustomEditorNoSSR;

This wrapper export the CustomEditor component with SSR disabled.

Step 4: Use the Creative Editor Component

You can import the non-SSR version of your editor like this:

import { default as CustomEditor } from './components/CustomEditorNoSSR';

Then, render it on the client by adding it to your JSX as usual:

<CustomEditor />

When you start your Next.js app and navigate to the page where <CustomEditor> is rendered, you’ll see the sample image appear on the canvas, along with a “Reduce Opacity” button. Every time you click the button, the image’s opacity decreases by 20%.

Use Cases

Congratulations! You’ve just unlocked the foundation for more advanced use cases, such as:

  • Building fully custom user interfaces using Next.js components alongside the CE.SDK canvas.
  • Automating the generation of graphics or marketing assets directly in the browser.
  • Integrating CE.SDK into complex, multi-step creative workflows using programmatic logic.
  • Creating server-side image or video processing features with @cesdk/node, as covered in our “Getting Started with CE.SDK in Node.js” guide.

Troubleshooting & Common Errors

❌ Error: Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client.

  • This warning appears only during development due to how Turbopack serves your app locally and how CE.SDK dynamically mounts DOM elements. It won’t show up in production. Thus, you can safely ignore this or suppress it by adding suppressHydrationWarning to the affected component.

❌ Error: document is not defined

  • This means CE.SDK tried to access the browser document object on the server. To avoid that, ensure CustomEditor is imported dynamically with next/dynamic and ssr: false, using the CustomEditorNoSSR wrapper.

❌ Error: The CE.SDK canvas is rendered twice

  • Although useEffect(..., []) is designed to execute only once, React might trigger a re-render in strict mode, causing the engine to load twice. Add an additional check (if (!engineRef.current) { ... }) to initialize the engine only if it hasn’t been loaded already.

❌ Error: CE.SDK canvas doesn’t render

  • Make sure that you’re appending engine.element to a valid DOM reference. Double-check that the reference is valid and is properly attached to the DOM element.

❌ Error: Module not found: Can't resolve '@cesdk/engine``'

  • Verify that you’ve correctly installed the CE.SDK Engine using the command npm install @cesdk/engine.

❌ Error: Editor engine could not be loaded: The License Key (API Key) you are using to access CE.SDK is invalid

  • Double-check that your license key is valid, hasn’t expired, and is correctly set.

❌ The component doesn’t behave as expected

  • Verify that your component paths and imports are correct.
  • See if use client is declared at the top of client components.
  • Check the browser console for specific errors.

Next Steps

This guide lays the foundation for: