Warum GrapesJS zu Remix passt
GrapesJS benötigt den DOM, also initialisierst du ihn in einer Remix-App darin
useEffect (nur für den Client) während die Loader und Aktionen von Remix Daten verarbeiten
auf dem Server. Diese Anleitung mountet den Editor, speichert durch eine Remix-Aktion und
exportiert HTML/CSS.
1. Den Editor clientseitig montieren
Erstellen app/routes/editor.tsxSie . Importiere GrapesJS im Effekt, damit es
Läuft während SSR nie.
import { useEffect, useRef } from 'react';
import 'grapesjs/dist/css/grapes.min.css';
export default function Editor() {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
let editor: any;
(async () => {
const grapesjs = (await import('grapesjs')).default;
editor = grapesjs.init({
container: ref.current!,
height: '100vh',
fromElement: false,
storageManager: false,
components: '<h1>Hello from GrapesJS</h1>',
});
// Persist via a Remix action.
document.getElementById('save')!.onclick = async () => {
await fetch('/editor', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
html: editor.getHtml(),
css: editor.getCss(),
project: editor.getProjectData(),
}),
});
};
})();
return () => editor?.destroy();
}, []);
return (
<>
<button id="save">Save</button>
<div ref={ref} />
</>
);
}
2. Den Rettungswurf in einer Remix-Aktion übernehmen
// same app/routes/editor.tsx
import type { ActionFunctionArgs } from '@remix-run/node';
import { json } from '@remix-run/node';
import { savePage } from '~/models/page.server';
export async function action({ request }: ActionFunctionArgs) {
const data = await request.json();
await savePage('home', data); // your DB write
return json({ status: 'ok' });
}
Die Aktion läuft auf dem Server, sodass dein Datenbankclient nicht im Browser bleibt Bündel.
3. Gespeicherte Inhalte wieder laden
export async function loader() {
return json(await getPage('home'));
}
// in the effect, after init:
// const saved = await fetch('/editor?_data=...'); editor.loadProjectData(saved.project);
Häufige Fallstricke in Remix
Die Aufteilung zwischen Server und Client bei Remix ist der Punkt, an dem die Dinge schiefgehen. Das Importieren von GrapesJS auf Modul-Top-Ebene führt es während SSR aus und lässt die Route abstürzen – importiere es stattdessen dynamisch im Inneren useEffect . Behalten Sie alle Persistenz auf der Route action (Server) und importieren Sie niemals Ihren Datenbankclient in die Komponente, sonst wird er ins Browser-Bundle gedrängt. Wenn du ein gespeichertes Projekt lädst, lies es in und loader ruf editor.loadProjectData() es nach init auf – versuche nicht, den Editor-Status während SSR zu rendern. Schließlich kehre vom Effekt zurück, editor.destroy() damit clientseitige Übergänge keine Editor-Instanzen stapeln.
Voraussetzungen
Du brauchst Node.js 18+ und eine Remix 2-App. Kein Remix-spezifisches GrapesJS-Paket ist verfügbar
erforderlich – der Editor ist nur browserfähig und die Loader/Actions von Remix verwalten Daten auf
Der Server. Vertrautheit mit Routen, useEffect, und Remix-Aktionen ist
Genug.
Fügen Sie benutzerdefinierte Blöcke zum Editor hinzu
Register ziehbare Blöcke mit dem Blockmanager nach init (innerhalb des Effekts):
editor.BlockManager.add('hero', {
label: 'Hero section',
category: 'Sections',
content: '<section class="hero"><h1>Headline</h1><p>Copy</p></section>',
});
Ziehe fertige Blockbibliotheken und Presets aus GJS.Market für ein reichhaltigeres Set.
Tieftauchen im Lager: Aktion + Lader
Behalte die Persistenz auf dem Server. POSTE das Projekt auf die Routen. action
Und lesen Sie es von der loader, damit Ihr Datenbank-Client niemals durchsickert.
In das Browser-Bundle:
export async function action({ request }) {
const data = await request.json();
await savePage('home', data); // server-only DB write
return json({ status: 'ok' });
}
export async function loader() {
return json(await getPage('home')); // returns the saved project
}
Nach Init rufen editor.loadProjectData(saved.project) Sie an, um eine Seite erneut zu öffnen.
Aufführungstipps
Importiere GrapesJS dynamisch im Inneren useEffect , damit es außerhalb der
Hauptbündel und den Server-Renderpfad sowie zurückgeben editor.destroy()
Client-seitige Übergänge stapeln also keine Instanzen. Code-Split-lastige Plugins dahinter
die Funktion, die sie nutzt.
Sicherheitsaspekte
Authentifizieren und autorisieren Sie die Handlung vor dem Schreiben – akzeptieren Sie niemals ein nicht authentifiziertes POST, das eine Seite überschreibt. Desinfizieren Sie gespeicherte Markup-Ausgaben beim Ausgang, falls Nicht-Administratoren können bearbeiten. Validiere die Nutzlastgröße, damit ein großes Projekt nicht erschöpft werden kann Erinnerung.
Fehlerbehebung häufiger Fehler
"Fenster/Dokument ist nicht definiert" bedeutet, dass GrapesJS während SSR lief —
importieren und es nur innerhalb useEffectiniten. Ein ungestylter
Canvas bedeutet, dass der Stylesheet-Import fehlt. Ein Blank
Editor bedeutet, dass die Container-Ref bei Init nicht bereit war. Hydrationswarnungen
bedeutet normalerweise, dass du versucht hast, den Editor-Status während SSR zu rendern.
Wann man GrapesJS mit Remix verwenden sollte
GrapesJS passt, wenn deine Remix-App eine echte visuelle Seite oder einen E-Mail-Builder einbettet Benutzerkontrolle, mit eigenem Speicher und HTML-Ausgabe. Für Inline-Rich Text gilt ein Ein leichterer Editor reicht aus; für ganzseitige Komposition mit Layout, Styling und Sauberer Export ist GrapesJS die stärkere, vom MIT lizenzierte und selbstgehostete Wahl.
Nächste Schritte
Siehe die zugehörigen GrapesJS + React und GrapesJS + Next.js Guides, durchstöbern Sie die GrapesJS-Marktplatz oder starte mit dem GJS.Market-Startseite.
FAQ
Funktioniert GrapesJS mit Remix-Server-Rendering?
Ja – initialisiere es nur in In useEffect , damit es im Browser läuft. Die
Die Route ist servergerendert; Nur die Editor-Instanz ist nur clientsbasiert.
Wie speichere ich GrapesJS-Daten in Remix?
POSTE die Projektdaten in eine Remix-Aktion und speichere sie in deiner Datenbank. Aktionen Laufe auf dem Server.
Wie lade ich gespeicherte Inhalte zurück?
Das gespeicherte Projekt aus einem Loader holen und aufrufen
editor.loadProjectData(saved) nach grapesjs.init.
