Baue einen visuellen Drag-and-Drop-Seiten-Builder in deiner Next.js-Anwendung mit GrapesJS, benutzerdefinierten Komponenten, persistenten Projektdaten und deinem eigenen Publishing-Workflow.
Block-Panel, Leinwand, Ebenen, Style-Manager und responsive Geräte – die Editor-Oberfläche, die GrapesJS dir gibt, bevor du eine Zeile davon schreibst.
Block-Panel, Leinwand, Ebenen, Style-Manager und responsive Geräte – die Editor-Oberfläche, die GrapesJS dir gibt, bevor du eine Zeile davon schreibst.
GrapesJS-Zahlen am 2026-09-03 anhand der npm-Registry und der GitHub-API geprüft. Die Kernbibliothek ist BSD-3-Clause; der offizielle React-Wrapper ist MIT.
Die Bau-vs-Adoptier-Frage
Baue einen Next.js-Seitenbauer, ohne die Editor-Engine zu bauen
GrapesJS stellt die visuelle Bearbeitungs-Engine bereit. Next.js stellt die Anwendungsarchitektur darum herum. Im Folgenden folgt dieselbe Liste, die zweimal gelesen wird: woraus ein Editor besteht und wer letztlich jedes Teil baut, sobald man GrapesJS einführt.
Drag & Drop — GrapesJS-Kern
Canvas — GrapesJS-Kern
Komponentenbaum — GrapesJS-Kern
Auswahl & Hover — GrapesJS-Kern
Blöcke — GrapesJS-Kern
Styling — GrapesJS-Kern
Responsive Bearbeitung — GrapesJS-Kern
Rückgängig / Wiederholen — GrapesJS-Kern
Asset-Verwaltung — GrapesJS-Kern
Serialisierung — GrapesJS-Kern
Befehle — GrapesJS-Kern
Plugin-System — GrapesJS-Kern
Speicher — GrapesJS-Kern
Mehrseitige Projekte — Plugin oder Erweiterung
Vorlagenbibliothek — Plugin oder Erweiterung
Benutzer & Berechtigungen — Deine Next.js-App
Veröffentlichung — Deine Next.js-App
Wer baut esDeine Next.js-AppGrapesJS-KernPlugin oder Erweiterung
Zwei der siebzehn gehören dir. Der Rest wird entweder mit dem Editor geliefert oder existiert als Plugin, das du installierst.
Ein Page Builder sieht aus wie eine Funktion und ist in Wirklichkeit ein kleines Produkt. Der sichtbare Teil – ein Block mit Blöcken, eine Leinwand, eine Stil-Seitenleiste – liegt auf einem Dutzend Subsystemen, die alle funktionieren müssen, bevor irgendetwas davon nutzbar erscheint.
Anstatt die Editor-Engine neu zu bauen, nutze GrapesJS und konzentriere dich auf dein Produkt.
Ergebnisse
Was kann man mit einem Next.js Page Builder bauen?
Die gleiche Editor-Engine unterstützt sehr unterschiedliche Produkte. Was sich zwischen ihnen ändert, sind die Blöcke, die man registriert, wer veröffentlichen darf und wohin die Ausgaben gelangen.
Ein echter GrapesJS-Editor, der auf dieser Seite läuft. Ziehe einen Block von rechts herein, wähle ein beliebiges Element auf dem Canvas aus und style es um, oder stelle den Canvas auf Handybreite — das ist die Oberfläche, die deine Nutzer bekämen.
Next.js besitzt Routing, Authentifizierung, Datenabruf und die bereitgestellte Oberfläche. GrapesJS besitzt alles innerhalb der Canvas. Dein API besitzt, was ein Projekt ist, wer es bearbeiten darf und wann es live geht. Jeder Abschnitt darunter ist eines dieser drei geöffneten Boxen.
Next.js kümmert sich um die Anwendung. GrapesJS übernimmt die visuelle Bearbeitung. Dein Backend übernimmt Persistenz und Publizierung. Nichts in dieser Kette verlangt, dass du auf deine bestehende Architektur verzichtest.
Architektur
Wie GrapesJS in eine Next.js-Anwendung passt
GrapesJS ist eine Schicht, kein Framework. Es rendert eine Leinwand in einen DOM-Knoten, den du ihm gibst, und stellt für alles auf dieser Leinwand ein API frei. Es hat keine Meinung zum Routing, keine Meinung zu deiner Datenbank und keine Laufzeitbeziehung zum Rest deiner App außer dem Element, das ihr übergeben wurde.
NEXT.JS
├──App Router
├──Authentication
├──Users
├──Permissions
├──API
├──Database
├──Billing
└──Publishing
↓
CLIENT EDITOR
↓
GRAPESJS
├──Canvas
├──Components
├──Blocks
├──Style Manager
├──Assets
├──Commands
└──Storage
Deshalb ist die Integration klein. Der Editor ist ein clientseitiges Widget mit einem reichen API – die gleiche Form wie ein Code-Editor oder eine Diagrammbibliothek, kein konkurrierendes Anwendungsframework. Alles, was dein Produkt zu deinem macht, bleibt auf der Next.js-Seite der Linie.
GrapesJS ersetzt Next.js nicht. Es fügt die visuelle Bearbeitungsebene hinzu, die Ihre Anwendung benötigt.
Schneller Start
Verwende GrapesJS mit dem Next.js App Router
GrapesJS benötigt einen Browser. Er misst Elemente, verbindet Zuhörer und mutiert den DOM in dem Moment, in dem er initialisiert wird, sodass der Editor in einen Client Component gehört und sein Setup in einen Effekt. Das ist die gesamte Einschränkung – alles andere ist gewöhnliches React.
npm install grapesjs
components/GrapesEditor.tsxtsx
'use client';
import { useEffect, useRef } from 'react';
import grapesjs, { type Editor } from 'grapesjs';
import 'grapesjs/dist/css/grapes.min.css';
export default function GrapesEditor() {
const containerRef = useRef<HTMLDivElement>(null);
const editorRef = useRef<Editor | null>(null);
useEffect(() => {
if (!containerRef.current) return;
// Runs only in the browser: effects never execute during SSR.
const editor = grapesjs.init({
container: containerRef.current,
height: '100vh',
storageManager: false,
blockManager: {
blocks: [
{
id: 'section',
label: 'Section',
content: '<section class="py-16"><h2>Headline</h2></section>',
},
{ id: 'text', label: 'Text', content: '<p>Edit me</p>' },
],
},
});
editorRef.current = editor;
// Strict Mode mounts twice in development; without this you get two editors.
return () => {
editor.destroy();
editorRef.current = null;
};
}, []);
return <div ref={containerRef} />;
}
app/editor/page.tsxtsx
import GrapesEditor from '@/components/GrapesEditor';
// A Server Component. It renders the Client Component; it never touches
// the editor instance, and no 'use client' is needed here.
export default function EditorPage() {
return <GrapesEditor />;
}
Warum das Beispiel so aussieht
'use client'
Markiert das Modul als Client Component, sodass React-Hooks verfügbar sind und der Code an den Browser gesendet wird.
useRef, nicht useState
Die Editor-Instanz ist keine Render-Daten. Wenn man sie in den Zustand versetzt, wird bei jeder Änderung ein erneutes Rendering ohne Nutzen geplant.
useEffect
Effekte laufen während des Server-Renderings nie, daher ist die Initialisierung erst garantiert, wenn der DOM existiert.
editor.destroy()
React Strict Mode mountet Komponenten zweimal während der Entwicklung. Ohne Aufräumen bekommt man zwei Editoren, die auf einem Knoten gestapelt sind.
Das ist ein funktionierender Editor. Der Rest dieser Seite behandelt die vier Dinge, die du als Nächstes hinzufügst: wo das Projekt gespeichert wird, was Nutzer ziehen können, was beim Veröffentlichen passiert und welche davon du nicht selbst schreiben musst.
Routing
App Router vs. Pages Router
Beides funktioniert. Sie unterscheiden sich darin, wo die Kundengrenze gezogen wird, und genau dieser Unterschied ist der Grund, warum so viele GrapesJS + Next.js-Ratschläge im Web bei modernen Projekten scheitern.
Empfohlen
App Router
Ein Client Component speichert den Editor; die Route darum herum bleibt ein Server Component. Es ist kein dynamischer Import erforderlich, da der Editor bereits nur im Browser initialisiert ist.
tsx
// components/GrapesEditor.tsx
'use client';
// …useRef + useEffect + grapesjs.init()
// app/editor/page.tsx — stays a Server Component
import GrapesEditor from '@/components/GrapesEditor';
export default function Page() {
return <GrapesEditor />;
}
Die 'use client'-Direktive markiert die Grenze – alles darunterliegende wird an den Browser gesendet.
Die Routendatei bleibt ein Server Component und kann auf Authentifizierung, Params und Daten warten, bevor der Editor gerendert wird.
Ein Client Component wird standardmäßig weiterhin auf dem Server vorgerendert. Effekte sind es nicht, was grapesjs.init()-Browser-only ermöglicht.
next/dynamic mit ssr: false wird innerhalb eines Server Component abgelehnt – Next.js sagt dir, es in ein Client Component zu verschieben.
Legacy
Pages Router
Jede Seite ist ein Client-Entry-Point, daher ist das übliche Rezept ein dynamischer Import mit deaktiviertem Prerendering.
pages/editor.tsxtsx
import dynamic from 'next/dynamic';
// In the Pages Router every page is a client entry point, so ssr: false
// is allowed here — and skips the prerender pass entirely.
const GrapesEditor = dynamic(() => import('@/components/GrapesEditor'), {
ssr: false,
loading: () => <p>Loading editor…</p>,
});
export default function EditorPage() {
return <GrapesEditor />;
}
ssr: false ist hier erlaubt und überspringt den Server-Renderpass komplett.
Die Ladeoption gibt dir einen Platzhalter, während der Editor-Chunk heruntergeladen wird.
Die gleiche GrapesEditor-Komponente funktioniert unverändert – nur die Art und Weise, wie sie importiert wird, unterscheidet sich.
Wenn du migrierst, nimm den Dynamic()-Wrapper nicht mit. Im App Router scheitert er entweder komplett innerhalb eines Server Component oder dupliziert die Arbeit, die 'use client' bereits macht.
Serverkomponenten
GrapesJS und React Server Components
Server Components ist der Ort, an dem der Editor umgeht. Sie holen Daten, führen serverseitige Logik aus und stellen die Anwendungsshell bereit. Der Client Component initialisiert GrapesJS, besitzt seinen Lebenszyklus und übernimmt jede Browserinteraktion. Props überschreiten die Grenze; die Editor-Instanz tut dies nie.
Wo die Grenze verläuft
Server Component
↓
Page / data
↓
Client Component
↓
GrapesJS editor
app/projects/[projectId]/editor/page.tsxtsx
import GrapesEditor from '@/components/GrapesEditor';
export default async function EditorPage({
params,
}: {
params: Promise<{ projectId: string }>;
}) {
const { projectId } = await params;
// Server side: auth, permissions and data fetching stay here.
const res = await fetch(`${process.env.API_URL}/projects/${projectId}`, {
cache: 'no-store',
});
const initialProject = await res.json();
// The Client Component receives plain, serialisable props.
return <GrapesEditor projectId={projectId} initialProject={initialProject} />;
}
Authentifizierungs- und Berechtigungsprüfungen laufen auf dem Server, bevor das Editor-Bundle sich zum Download lohnt.
Die Anfangsdaten des Projekts werden serverseitig abgerufen und als schlichte, serialisierbare Prop weitergegeben.
Die Editor-Instanz bleibt im Client Component. Nichts daran ist serialisierbar, also überschreitet nichts daran die Grenze.
Server Actions kann aus der Client-Komponente für Speicherstände aufgerufen werden – sie sind einfach Funktionen auf der Clientseite der Grenze.
Behalte den Editor clientseitig, während du Server Components für die umliegende Anwendung verwendest.
SSR
Funktioniert GrapesJS mit Next.js SSR?
Ja, aber der Editor selbst sollte im Client initialisiert werden, weil er auf Browser-APIs und DOM angewiesen ist.
Die nützliche Version dieser Antwort ist spezifischer, weil die beiden Hälften unterschiedlich versagen.
Vor Ort reproduziert
runtime
Node 20, no DOM
grapesjs
0.23.6
import
import('grapesjs') → resolves
init
grapesjs.init() → ReferenceError: document is not defined
2026-09-02
Das Importieren der Bibliothek in eine Laufzeitumgebung ohne DOM ist in Ordnung. Das Aufrufen von init() ist es nicht. Die Regel lautet also nicht "GrapesJS vom Server fernhalten" – sondern "init() aus dem Renderpfad heraushalten", was ein Effekt bereits garantiert.
Der Import ist sicher. Das Bündeln von GrapesJS in ein Modul, das der Server ebenfalls auswertet, wirft nicht.
Die Initialisierung ist es nicht. grapesjs.init() liest Dokumente, also muss es nach der Montage laufen – genau das bedeutet useEffect.
'use client' ist nicht dasselbe wie "nur für den Client". Client Components werden auf dem Server vorgerendert; der Effekt ist, was dort nicht ausgeführt wird.
dynamic(..., { ssr: false }) ist daher im App Router optional. Greifen Sie dazu, um den Editor-Abschnitt aus der ursprünglichen Nutzlast zu halten, nicht um einen Absturz zu beheben.
Das Rendern einer veröffentlichten Seite ist ein ganz anderes Problem – siehe unten. Diese Seite benötigt überhaupt keine Editor-Laufzeit, sodass sie wie alles andere statisch generiert werden kann.
Verfassen vs. Servieren
Ihr Redakteur muss nicht Ihre veröffentlichte Seite sein
Das ist das Nützlichste, was man früh richtig machen sollte, und am einfachsten falsch zu machen. Der Editor ist eine Autorenumgebung. Die veröffentlichte Seite kann Ihre eigene Next.js-Rendering-Architektur verwenden – statische Generierung, Streaming, Revalidierung, Edge Caching, alles.
Autorenarbeit
GrapesJS
↓
Project data
↓
Database
Servieren
Publish
↓
HTML + CSS
↓
Public page
Zwei Pipelines, ein Artefakt, das zwischen ihnen verläuft. Nur die erste benötigt GrapesJS.
app/p/[slug]/page.tsxtsx
// The public route. GrapesJS is not imported here, so the editor
// bundle never reaches a visitor.
export const revalidate = 3600;
export default async function PublishedPage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const res = await fetch(`${process.env.API_URL}/published/${slug}`, {
next: { revalidate: 3600 },
});
// Already sanitised on the way in — see the publish route below.
const { html, css } = await res.json();
return (
<>
<style dangerouslySetInnerHTML={{ __html: css }} />
<div dangerouslySetInnerHTML={{ __html: html }} />
</>
);
}
GrapesJS muss nie auf einer Seite laufen, die ein Besucher öffnet. Schick den Editor an die wenigen Leute, die bearbeiten, und schicke einfach HTML und CSS an alle anderen.
Persistenz
Speichere GrapesJS-Projekte in deiner Next.js-Anwendung
GrapesJS liefert lokale und entfernte Speicheradapter aus und ermöglicht es, eigene Adapter zu registrieren. Ein individueller Adapter ist meist die richtige Wahl, weil er jedes Lesen und Schreiben hinter eine Route legt, die du steuerst und authentifizieren kannst.
GrapesJS
↓→
Project data
↓→
Next.js API
↓→
Your backend
↓→
Database
Der Editor spricht nie mit deiner Datenbank. Er spricht mit einer Route, die wiederum mit dem spricht, was du tatsächlich benutzt.
components/GrapesEditor.tsx — Speicherts
const editor = grapesjs.init({
container: containerRef.current,
storageManager: {
type: 'nextjs-api',
autosave: true,
stepsBeforeSave: 5,
},
plugins: [
// Registered as a plugin so the adapter exists before the first load.
(editor) => {
editor.Storage.add('nextjs-api', {
async load() {
const res = await fetch(`/api/projects/${projectId}`);
return res.ok ? res.json() : {};
},
async store(project) {
await fetch(`/api/projects/${projectId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(project),
});
},
});
},
],
});
app/api/projects/[projectId]/route.tsts
import { NextResponse } from 'next/server';
// Your own persistence layer — Postgres, MySQL, Mongo, S3, a headless CMS.
// GrapesJS never talks to it; it only ever talks to this route.
import { loadProject, saveProject } from '@/lib/projects';
import { requireProjectAccess } from '@/lib/auth';
export async function GET(
_request: Request,
{ params }: { params: Promise<{ projectId: string }> },
) {
const { projectId } = await params;
await requireProjectAccess(projectId);
return NextResponse.json(await loadProject(projectId));
}
export async function PUT(
request: Request,
{ params }: { params: Promise<{ projectId: string }> },
) {
const { projectId } = await params;
await requireProjectAccess(projectId);
const project = await request.json();
if (typeof project !== 'object' || project === null) {
return NextResponse.json({ error: 'Invalid project' }, { status: 400 });
}
await saveProject(projectId, project);
return NextResponse.json({ ok: true });
}
Laden: Das load() des Adapters läuft auf init und stellt das Projekt wieder, an dem der Nutzer zuletzt gearbeitet hat.
Speichern: store() erhält das gesamte Projekt als JSON. Gib ein abgelehntes Versprechen zurück, um einen Fehler im Editor aufzudecken.
Autosave: Autosave mit stepsBeforeSave-Batch-Änderungen, sodass du nicht bei jedem Tastendruck schreibst.
Entwürfe: Bewahren Sie das Entwurfsprojekt und die veröffentlichten Ergebnisse in getrennten Spalten auf, damit das Bearbeiten niemals verändert, was Besucher sehen.
Versionskontrolle: Projektdaten sind ein JSON-Dokument – eine nur anhängbare Versionstabelle kostet einen Insert und kauft Rollback.
Veröffentlichen: Ein separater Endpunkt mit eigener Berechtigungsprüfung, kein Flag auf der Speicherroute.
Beispiel: Projektspeicherung mit Supabase
lib/projects.tsts
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!, // server only — never NEXT_PUBLIC_
);
export async function loadProject(projectId: string) {
const { data } = await supabase
.from('projects')
.select('project')
.eq('id', projectId)
.single();
return data?.project ?? {};
}
export async function saveProject(projectId: string, project: unknown) {
await supabase
.from('projects')
.upsert({ id: projectId, project, updated_at: new Date().toISOString() });
}
Dies implementiert die Grenze von loadProject und saveProject, die die obige Route importiert. Tausche den Body gegen Prisma, Drizzle, Mongo, DynamoDB oder einen REST-Call auf ein bestehendes Backend, und sonst ändert sich auf der Seite nichts.
Beispiel: Projektspeicher mit einer Vercel/Next.js-Bereitstellung verbinden
Publishing schreibt HTML und CSS, deine öffentliche Route wird zurückgelesen. Revalidierung, Caching und die Rendering-Strategie bleiben gewöhnliche Next.js-Probleme – der Editor befindet sich nicht im Anfragepfad.
Du kannst GrapesJS mit jedem Backend oder jeder Datenbank verbinden. Nichts im Editor weiß oder interessiert, welchen du gewählt hast.
Erweiterung der Leinwand
Erstellen Sie benutzerdefinierte Komponenten für Ihren Next.js Page Builder
Direkt aus der Verpackung bietet die Leinwand den Nutzern generisches HTML. Benutzerdefinierte Komponententypen sind der Weg, wie der Editor stattdessen das Markup deines Produkts erstellt – dieselben Abschnitte, die deine Next.js-App bereits rendert, mit nur den Eigenschaften, die du freilegst.
Hero — Schlagzeile, Unterüberschrift, Hintergrund und ein Aufruf zum Handeln
Preistabelle — Pläne, Preise und Abrechnungszeitraum als editierbare Felder
Produktkarte – an eine echte Produkt-ID gebunden, statt an eingegebenen Text
Feature-Raster – ein festes Layout mit variabler Anzahl von Kindern
Formular – Felder, die Nutzer anordnen können, verbunden mit Ihrem Einreichungsendpunkt
Navigation – von deinen Routen abgezogen, damit die Links nicht veraltet werden
Anwendungskomponent – ein Diagramm, ein Buchungs-Widget, alles, was dein SaaS bereits ausgeliefert hat
Benutzerdefinierte Komponenten bestimmen, wie der Editor Ihr Designsystem und Ihr Geschäftsmodell anpasst. droppable: false verhindert, dass ein Nutzer eine Struktur zerlegt; traits entscheidet genau, welche Eigenschaften er ändern darf.
Baue eine Blockbibliothek
Blöcke sind das, was im Panel erscheint, von dem ein Nutzer zieht. Das Registrieren eines solchen Blocks kostet ein paar Zeilen, sobald der Komponententyp existiert.
Registrierung eines Blocksts
// A block is what the user drags. A component is what it becomes.
editor.Blocks.add('pricing-table', {
label: 'Pricing table',
category: 'Marketing',
media: '<svg viewBox="0 0 24 24" width="24" height="24">' +
'<rect x="3" y="4" width="18" height="16" rx="2" fill="currentColor"/></svg>',
content: { type: 'pricing-table' },
});
Blöcke sind keine Komponenten
Ein Block ist ein Ausgangspunkt – ein Label, ein Symbol und der Inhalt, den er einfügt. Er existiert im Panel, nicht auf der Seite.
Eine Komponente ist ein strukturiertes Element innerhalb des Editors, mit eigenem Modell, traits und Regeln. Sie existiert auf der Seite, sobald sie eingefügt wurde.
Ein Komponententyp kann mehrere Blöcke unterstützen (eine zweispaltige und eine dreispaltige Preistabelle), und ein Block kann einen ganzen Baum von Komponenten einfügen.
Anwendungsblöcke – was auch immer dein Produkt tut, das sollte eine Seite zeigen können.
Verwalten Sie Bilder und Assets
Der Asset Manager übernimmt Auswahl und Einfügung. Wo sich die Dateien befinden, wer sie hochladen darf und was durchgelassen wird, ist deine Seite des Vertrags.
Asset Manager — benutzerdefinierter Uploadts
assetManager: {
// The library the user already has, loaded from your backend.
assets: initialAssets,
uploadFile: async (event) => {
const files = event.dataTransfer
? event.dataTransfer.files
: (event.target as HTMLInputElement).files;
if (!files) return;
const body = new FormData();
Array.from(files).forEach((file) => body.append('files', file));
// Your route authenticates the user and validates type and size
// before anything is written to storage.
const res = await fetch('/api/assets', { method: 'POST', body });
const { urls } = await res.json();
editor.AssetManager.add(urls);
},
},
GrapesJS Asset Manager
↓
Next.js API
↓
Your storage
↓
CDN
Uploads verlassen den Browser einmal und landen auf einer von dir geschriebenen Route.
Lade über deinen eigenen Routenhandler hoch, damit das Session-Cookie, das Rate-Limit und das Audit-Log alle gelten.
Erlauben Sie externe URLs für Teams, die bereits Bilder anderswo hosten.
Laden Sie die bestehende Bibliothek des Nutzers vor, sodass der Picker auf seinen Assets öffnet und nicht auf einem leeren Panel.
Validiere den MIME-Typ und die Größe auf dem Server. Die Dateieingabe des Editors ist ein Vorschlag, keine Steuerung.
Serviere von einem CDN aus und speichere die CDN-URL im Projekt, damit veröffentlichte Seiten nie auf deinen Ursprung für Medien stoßen.
Große Bibliotheken mit Paginierungen – das Asset-Panel versucht gerne, zehntausend Vorschaubilder zu rendern.
Datenmodell
Projektdaten vs. HTML und CSS
Der Editor erzeugt zwei verschiedene Dinge, die nicht austauschbar sind. Das falsche Speichern ist der Fehler, der "Seite bearbeiten" in "neu anfangen" umwandelt.
Projektdaten
Ein JSON-Dokument, das Komponenten, Stile, Seiten und Assets beschreibt. Dies ist die editierbare Quelle – das einzige Artefakt, das eine Bearbeitungssitzung genau so wiederherstellen kann, wie der Nutzer sie verlassen hat.
HTML
Der gerenderte Inhalt der Leinwand. Perfekt, um Besucher zu servieren, verlustbehaftet als Quelle: Die Komponententypen, traits und der Editor-Status sind verschwunden.
CSS
Das vom Editor generierte Stylesheet, einschließlich der Regeln für jeden Breakpoint. Wurde zusammen mit dem HTML bedient und wurde beim Veröffentlichen neu generiert.
Beide Artefakte, von einem Herausgeberts
// Project data — the editable source. This is what you store.
const project = editor.getProjectData();
// HTML and CSS — the rendered output. Generate this when you publish.
const html = editor.getHtml();
const css = editor.getCss();
// Reopening an editing session needs the project data, not the HTML:
editor.loadProjectData(project);
Schnitt
User edits
↓
Project data
↓
Database
Rendering
Project data
↓
HTML + CSS
↓
Preview / publish
Speichere Projektdaten zum Bearbeiten. Generiere HTML/CSS, wenn du Inhalte veröffentlichen oder rendern musst.
Arbeitsablauf
Vom Entwurf zur veröffentlichten Seite
1
Bearbeitung
Der Benutzer arbeitet im Editor
Veränderungen sammeln sich in den Projektdaten. Nichts, was ein Besucher sieht, hat sich bisher bewegt.
2
Entwurf speichern
Autosave schreibt das Projekt
Dein Speicheradapter überträgt das Projekt-JSON bei einem Debounce oder einer Schrittzählung an deinen API.
3
Vorschau
Stellen Sie den Entwurf so dar, wie es ein Besucher tun würde.
Eine private Route, die HTML und CSS aus dem Entwurfsprojekt generiert – denselben Codepfad für die Veröffentlichung, aber ohne das Schreiben.
4
Genehmigung
Wer auch immer die Seite besitzt, unterschreibt sie
Optional und es lohnt sich, beim ersten Veröffentlichungsbericht einer defekten Preistabelle hinzugefügt zu werden.
5
Veröffentlichen
Die Ausgabe einfrieren
Generiere HTML und CSS, desinfiziere, speichere sie als neue Version und richte den Live-Slug darauf.
6
Rollback
Richte die Kugel auf eine frühere Version
Billig, wenn jede Veröffentlichung ein Insert und kein Update wäre. Teuer, später hinzuzufügen.
App/API/Veröffentlichen/route.tsts
import { NextResponse } from 'next/server';
import sanitizeHtml from 'sanitize-html';
import { publishPage } from '@/lib/projects';
import { requireProjectAccess } from '@/lib/auth';
export async function POST(request: Request) {
const { projectId, html, css } = await request.json();
// Permissions are enforced here, not by hiding a button in the editor.
const user = await requireProjectAccess(projectId);
if (typeof html !== 'string' || typeof css !== 'string') {
return NextResponse.json({ error: 'Invalid payload' }, { status: 400 });
}
// Sanitise on the way in, once — not on every render.
const page = await publishPage({
projectId,
html: sanitizeHtml(html),
css,
publishedBy: user.id,
});
return NextResponse.json({ url: `/p/${page.slug}` });
}
Katalog
Erweitern Sie Ihren Next.js Page Builder mit Plugins
GrapesJS stellt die Editor-Engine bereit. Plugins fügen die spezialisierte Funktionalität hinzu, die ein bestimmtes Produkt benötigt – und das meiste, was einem Page Builder nach der ersten Woche noch fehlt, existiert bereits als solche.
Plugins beseitigen keine Entwicklungsarbeit. Sie verändern, welche Art von Arbeit es ist, und dieser Unterschied verstärkt sich über die Lebensdauer des Produkts.
Intern verfasst
Bau es selbst
Jede Fähigkeit, die du schreibst, wird zu einem dauerhaften Zeilenpost.
Aus dem Katalog
Installiere ein Plugin
Jemand hat bereits die generische Hälfte deines Editors gelöst.
Nutze den GrapesJS-Kern für den Editor und füge nur die Funktionen hinzu, die dein Produkt tatsächlich benötigt.
Die Entscheidung
Einen Next.js Page Builder von Grund auf neu bauen oder GrapesJS verwenden?
Fähigkeit für Fähigkeit ist das, was du schreiben möchtest. "Erweiterbar" bedeutet, dass die Bibliothek die Schnittstelle und die Standard-Oberfläche bereitstellt und erwartet, dass du sie auf dein eigenes Backend verlegst.
Leistungsfähigkeit
Bau dich selbst auf
GrapesJS
Leinwand
Selbst bauen
Enthalten
Drag & Drop
Selbst bauen
Enthalten
Komponenten
Selbst bauen
Enthalten
Blöcke
Selbst bauen
Enthalten
Design
Selbst bauen
Enthalten
Responsive Bearbeitung
Selbst bauen
Enthalten
Vermögenswerte
Selbst bauen
Erweiterbar
Lagerung
Selbst bauen
Erweiterbar
Kommandos
Selbst bauen
Enthalten
Plugins
Ökosystem aufbauen
Plugin-Architektur
Vergleichbar mit dem veröffentlichten GrapesJS API auf 2026-09-02.
Next.js gibt dir das Anwendungs-Framework. GrapesJS gibt dir die visuelle Bearbeitungs-Engine.
Kommerzielle Produkte
Baue einen SaaS Page Builder mit Next.js
Ein SaaS Page Builder ist deine Anwendung mit einem Editor darin. Fast alles, was es zu einem Geschäft macht – Konten, Teams, Limits, Abrechnung, Domains – ist Next.js-Arbeit, die du sowieso machen würdest. Der Editor ist ein Weg.
NEXT.JS
├──Authentication
├──Organizations
├──Users
├──Permissions
├──Billing
└──Editor
↓
GRAPESJS
↓
PROJECT API
├──Database
└──Publishing
Multi-User: Mehrere Personen, die verschiedene Projekte bearbeiten, und schließlich dasselbe.
Berechtigungen: wer bearbeiten darf, wer veröffentlichen darf, wer nur schauen darf. Serverseitige Erzwingungen.
Organisationen und Teams: Projekte gehören einem Konto, nicht einer Person.
Abrechnung: Planlimits, ausgedrückt als Anzahl von Projekten, Seiten, Sitzplätzen oder veröffentlichten Domains.
Publishing: der Schritt, in dem Ihr Produkt die Verantwortung für das Veröffentlichen übernimmt.
White-Label: Ihre Panels, Ihre Icons, Ihre Farben – der Editor sollte nicht aufgebaut wirken.
Der Editor ist ein Weg in deiner Bewerbung. Alles drumherum ist das, was du tatsächlich verkaufst.
Bevor du verschiffst
Leistungs- und Sicherheitsaspekte
Zwei Listen, die es wert sind, gelesen zu werden, bevor der erste echte Nutzer den Editor öffnet, nicht danach.
Leistung
Halte den Redakteur vom schnellen Weg fern
GrapesJS ist eine umfangreiche clientseitige Bibliothek. Das ist auf dem Autorenweg in Ordnung und überall sonst teuer.
✓Lazy-Load GrapesJS, sodass sein Chunk über die Editor-Route und nicht über die App-Shell abgerufen wird.
✓Initialisieren Sie nur, wenn der Editor tatsächlich benötigt wird – nicht auf einem Dashboard, das nur darauf verlinkt.
✓Halte die Editor-Instanz in einer Referenz, außerhalb des React-Zustands.
✓Vermeide es, die Komponente neu zu rendern, die den Editor besitzt; der GrapesJS-Lebenszyklus ist nicht der von React.
✓Lazy-load-lastige Plugins statt alle bei init zu registrieren.
✓Paginieren Sie große Asset-Sammlungen, anstatt die gesamte Bibliothek in das Panel zu laden.
Der Editor ist ein Autorenwerkzeug. Normalerweise braucht man nicht die vollständige Editor-Laufzeit auf jeder veröffentlichten Seite.
Sicherheit
Behandle die Editor-Ausgabe als Benutzereingabe
Weil es so ist. Alles, was die Leinwand produzierte, kam über das Netzwerk aus einem Browser, den du nicht kontrollierst.
✓Desinfizieren Sie generierte oder vom Nutzer bereitgestellte HTML, bevor Sie es speichern, dort, wo es als Markup gerendert wird.
✓Validiere hochgeladene Assets serverseitig: MIME-Typ, Größe und was du bereit bist zurückzugeben.
✓Authentifiziere jeden Speicherendpunkt. Eine Projekt-ID in einer URL ist keine Autorisierung.
✓Berechtigungen serverseitig durchsetzen; ein versteckter Veröffentlichen-Button ist eine UI-Präferenz, keine Kontrolle.
✓Validiere die Form der Projektdaten, bevor du sie schreibst, und noch einmal, bevor du daraus renderst.
✓Schützen Sie die Veröffentlichungs-Endpunkte getrennt vom Speichern – sie haben unterschiedliche Explosionsradien.
✓Setze ein Content Security Policy für die Routen, die veröffentlichtes Markup rendern.
Vertraue niemals dem Zustand des clientseitigen Editors. Der Editor ist eine Bequemlichkeit für den Autor, keine Grenze.
Fehlerbehebung
Häufige Fehler bei Next.js + GrapesJS
Fast jeder für diesen Stack gemeldete Integrationsfehler gehört zu den folgenden sieben.
✕Fehler
Initialisierung von GrapesJS innerhalb eines Server Component
Server Components hat keine Hooks, keine Effekte und kein DOM. Der Import kann sich lösen, aber es gibt keinen Ort, an dem der Editor montieren kann.
✓Lösung
Benutze ein Client Component – setze 'use client' ganz oben in das Modul, das den Editor besitzt.
✕Fehler
Initialisierung vor dem Vorhandensein des DOM
Das Aufrufen von init() während des Renderings oder gegen eine Ref, die noch null ist, schlägt fehl, weil das Containerelement noch nicht erstellt wurde.
✓Lösung
Initialisiere nach der Mount, innerhalb von useEffect, und bewahre die Referenz, wenn sie anwesend sind.
✕Fehler
GrapesJS während SSR ausführen
Jeder Aufruf von init(), der den Server erreicht, wirft das Dokument ReferenceError: ist nicht definiert. Der Import allein ist harmlos.
✓Lösung
Behalte die Editor-Initialisierung clientseitig. Ein Effekt reicht aus; dynamische Importe sind eine Optimierung, keine Lösung.
✕Fehler
Die Editor-Instanz in den React-Zustand versetzen
Der Editor ist ein großes, veränderbares Objekt, das sich ständig ändert. Wenn man ihn in Zustandsplänen speichert, wird das Rendern erneuert, die nichts erreichen.
✓Lösung
Verwende eine Referenz. Behalte den Zustand für Dinge, die der UI tatsächlich rendert, wie zum Beispiel einen Speicherindikator.
✕Fehler
Das unnötige Wiederrendern des Editors
Ein Parent-Rerendering, das Requisiten nachstellt oder den Container neu montiert, zerlegt und baut die gesamte Leinwand wieder auf.
✓Lösung
Halte den GrapesJS-Lebenszyklus getrennt vom normalen React-Rendering – mounte einmal, dann führe es durch sein eigenes API.
✕Fehler
Nur HTML speichern
HTML ist die Ausgabe, nicht die Quelle. Ein erneutes Öffnen eines Projekts aus HTML verliert Komponententypen, traits und den Editor-Zustand, sodass die nächste Bearbeitung von einer abgeflachten Seite beginnt.
✓Lösung
Speichern Sie Projektdaten, falls Benutzer weiter bearbeiten müssen. Erstellen Sie HTML beim Veröffentlichen.
✕Fehler
GrapesJS auf jeder öffentlichen Seite laufen zu lassen
Die Editor-Laufzeit an Besucher zu verschicken, die nichts bearbeiten können, kostet Bündelgröße, Speicher und Core Web Vitals ohne Rückgabe.
✓Lösung
Trennen Sie die Editor-Laufzeit von veröffentlichten Inhalten – veröffentlichen Sie die veröffentlichten Seiten als einfache HTML und CSS.
Fahrplan
Fang klein an, dann skaliere
Drei Zielfernrohre, jedes ergänzt das vorherige. Die meisten Teams überschießen das erste und werden vom dritten überrascht.
1Woche eins
MVP
Beweise, dass die Lektoratserfahrung richtig ist, bevor du etwas darum aufbaust.
Der Weg vom Prototyp zum kommerziellen Produkt verläuft über Ihre Anwendung, nicht über den Editor.
FAQ
Häufig gestellte Fragen
Kann ich GrapesJS mit Next.js verwenden?
Ja. GrapesJS ist eine clientseitige Bibliothek, die in ein DOM-Element eingebunden wird, sodass sie in jeder React-Anwendung läuft, einschließlich Next.js. Installiere grapesjs, initialisiere es in einem Effekt innerhalb eines Client Component und zerstöre es beim Unmounten.
Funktioniert GrapesJS mit dem Next.js App Router?
Ja, und die App Router ist die empfohlene Integration. Setze 'use client' oben in die Komponente, die den Editor besitzt, und initialisiere GrapesJS in useEffect. Die Routendatei selbst kann ein Server Component bleiben.
Funktioniert GrapesJS mit React Server Components?
Er arbeitet parallel zu ihnen. Der Editor selbst kann kein Server Component sein – er braucht Hooks, Effekte und das DOM. Server Components verwaltet Authentifizierung, Datenabruf und die Page Shell, und übergibt dann serialisierbare Requisiten an den Client Component, der den Editor besitzt.
Unterstützt GrapesJS Next.js SSR?
Das Komponenten-Wrapping des Editors kann servergerendert werden; der Editor selbst muss im Browser initialisieren. grapesjs.init() liest das Dokument und fügt eine Node-Laufzeit ein, daher sollte dieser Aufruf in einem Effekt bleiben. Veröffentlichte Seiten benötigen überhaupt keine Editor-Laufzeit und können statisch generiert werden.
Warum braucht GrapesJS einen Client Component?
Weil er direkt mit dem DOM interagiert – Elemente misst, Zuhörer anbindet und eine iframe-Leinwand rendert – und weil React-Hooks benötigt werden, um seinen Lebenszyklus zu verwalten. Beides ist in einem Server Component nicht verfügbar.
Wie initialisiere ich GrapesJS in Next.js?
Erstelle ein Client Component mit einer Referenz auf einem Container-Div, rufe grapesjs.init({ container }) innerhalb von useEffect mit einem leeren Dependenzarray auf, behalte den zurückgegebenen Editor in einer zweiten Referenz und rufe editor.destroy() in der Reinigungsfunktion auf.
Sollte ich dynamic(..., { ssr: false }) verwenden?
Im Pages Router ja – es ist die Standardmethode, das Prerendering zu überspringen. Im App Router ist es optional und kann nicht in einem Server Component verwendet werden: Next.js lehnt ssr: false dort ab und bittet dich, es in ein Client Component zu übertragen. Nutze es nur im App Router, um den Editor-Abschnitt aus der ursprünglichen Nutzlast zu halten.
Wie speichere ich GrapesJS-Projekte in Next.js?
Registrieren Sie einen benutzerdefinierten Speicheradapter bei editor.Storage.add(), dessen load() und store() einen Next.js-Routenhandler aufrufen. Die Route authentifiziert die Anfrage und liest oder schreibt das Projekt-JSON in Ihrer Datenbank. Aktivieren Sie das automatische Speichern mit stepsBeforeSave, damit die Schreibvorgänge gestapelt werden.
Kann ich Supabase mit GrapesJS und Next.js verwenden?
Ja, und auf dieser Seite gibt es ein Beispiel. Supabase ist eine Implementierung der Lade-/Speichergrenze – Postgres, MySQL, Mongo, S3 oder ein bestehendes REST API funktionieren genauso, weil der Editor immer nur mit deiner Route spricht.
Kann ich einen SaaS-Seitenbauer mit Next.js und GrapesJS bauen?
Ja. GrapesJS ist BSD-3-Clause-lizenziert und selbstgehostet, ohne Gebühr pro Sitz und ohne gehosteten Dienst im Loop, sodass man es in ein kommerzielles Produkt einbetten kann. Die Arbeit besteht hauptsächlich aus der SaaS-Schicht – Organisationen, Berechtigungen, Abrechnung, Veröffentlichung – was normale Next.js-Arbeit ist.
Kann ich eigene Blöcke erstellen?
Ja. editor.Blocks.add() registriert einen Block mit einem Label, einer Kategorie, einem Icon und dem Inhalt, den er einfügt. Blöcke können rohes HTML einfügen oder einen eigenen Komponententyp instanziieren.
Kann ich benutzerdefinierte Komponenten erstellen?
Ja. editor.Components.addType() definiert einen Komponententyp mit eigenem Modell, traits, Regeln und Kinderstruktur – den Mechanismus, um dein Designsystem innerhalb der Leinwand freizulegen und gleichzeitig zu verhindern, dass Nutzer es kaputt machen.
Kann ich HTML und CSS exportieren?
Ja. editor.getHtml() und editor.getCss() geben jederzeit die Leinwandausgabe zurück, und getProjectData() liefert den editierbaren JSON-Quellcode. Speichere die Projektdaten für die Bearbeitung; erstelle HTML und CSS beim Veröffentlichen.
Kann ich GrapesJS-Plugins mit Next.js verwenden?
Ja. Plugins sind Funktionen, die die Editor-Instanz empfangen, sodass sie in Next.js genauso registriert werden wie überall sonst – über die Plugins-Option bei init oder indem man den Editor API von deinem onEditor-Handler aufruft.
Kann ich mit Next.js einen CMS-Editor bauen?
Ja, und es ist eine der häufigsten Verwendungszwecke. Map GrapesJS-Projektdaten auf dein bestehendes Inhaltsmodell ein, beschränke das Blockset auf Komponenten, die deine Next.js-Vorlagen rendern können, und lasse Editoren visuell arbeiten, ohne das Repository zu berühren.
Kann ich einen Next.js-Seitengenerator selbst hosten?
Ja. GrapesJS ist ein npm-Paket ohne gehostetes Backend, Lizenzserver oder Telemetrie, sodass der gesamte Builder – Editor, Speicher und veröffentlichte Seiten – überall dort läuft, wo Ihre Next.js-Anwendung läuft.
Baue deinen Next.js Page Builder mit GrapesJS
Nutze Next.js für deine Anwendungsarchitektur und GrapesJS für die visuelle Bearbeitungs-Engine. Beginne mit dem Kerneditor und erweitere dein Produkt mit den Plugins, Blöcken und Integrationen, die du brauchst.