Por qué GrapesJS encaja con Remix
GrapesJS necesita el DOM, así que en una app de Remix lo inicializas dentro de la app
useEffect (solo para clientes) mientras que los cargadores y acciones de Remix gestionan los datos
en el servidor. Esta guía monta el editor, guarda mediante una acción de Remix, y
exporta HTML/CSS.
1. Montar el editor en el lado del cliente
Crear app/routes/editor.tsx. Importa GrapesJS dentro del efecto para que sea
nunca se ejecuta durante SSR.
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. Gestionar la partida guardada en una acción de Remix
// 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' });
}
La acción se ejecuta en el servidor, por lo que tu cliente de base de datos permanece fuera del navegador Un paquete.
3. Cargar contenido guardado de nuevo
export async function loader() {
return json(await getPage('home'));
}
// in the effect, after init:
// const saved = await fetch('/editor?_data=...'); editor.loadProjectData(saved.project);
Trampas comunes en Remix
La división servidor/cliente de Remix es donde las cosas se torcen. Importar GrapesJS en el nivel superior del módulo lo ejecuta durante SSR y la ruta se bloquea — impórtalo dinámicamente dentro useEffect de la ruta. Mantén toda la persistencia en las action rutas (servidores) y nunca importe tu cliente de base de datos al componente, porque se filtra en el paquete del navegador. Si cargas un proyecto guardado, léelo en el loader y llama editor.loadProjectData() después de init — no intentes renderizar el estado del editor durante el SSR. Por último, regresa editor.destroy() del efecto para que las transiciones del lado del cliente no apilen instancias del editor.
Requisitos previos
Necesitarás Node.js 18+ y una app de Remix 2. No hay ningún paquete específico de Remix de GrapesJS
requerido — el editor es solo para navegador y los cargadores/acciones de Remix gestionan los datos en
El camarero. La familiaridad con las rutas, useEffect, y acciones de Remix es
Basta.
Añadir bloques personalizados al editor
Registrar bloques arrastrables con el Gestor de bloques después de iniciar (dentro del efecto):
editor.BlockManager.add('hero', {
label: 'Hero section',
category: 'Sections',
content: '<section class="hero"><h1>Headline</h1><p>Copy</p></section>',
});
Saca librerías de bloques y presets listos de GJS. Haz marketing para un conjunto más rico.
Inmersión en el almacenamiento: acción + cargador
Mantén la persistencia en el servidor. POST el proyecto a las rutas action
y léelo de nuevo desde el loader, para que tu cliente de base de datos nunca filtre
En el paquete del navegador:
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
}
Después de la iniciativa, llama editor.loadProjectData(saved.project) para reabrir una página.
Consejos de rendimiento
Importa GrapesJS dinámicamente dentro useEffect para que no entre en el
main bundle y la ruta de renderizado del servidor, y return editor.destroy()
Así que las transiciones del lado del cliente no apilan instancias. Plugins con mucho código dividido detrás
La función que los utiliza.
Consideraciones de seguridad
Autentica y autoriza la acción antes de escribir — nunca aceptes un POST no autenticado que sobrescribe una página. Sanitize almacenaba el margen en la salida si Los que no son administradores pueden editar. Valida el tamaño de la carga útil para que un proyecto grande no pueda agotarse memoria.
Resolución de errores comunes
"window/document is not defined " significa que GrapesJS se ejecutó durante SSR —
Importarla e initar solo dentro useEffect. Un sin estilo
Canvas significa que falta la importación de la hoja de estilo. Un blank
editor significa que la referencia del contenedor no estaba lista en init. Advertencias de hidratación
normalmente significa que intentaste renderizar el estado del editor durante SSR.
Cuándo usar GrapesJS con Remix
GrapesJS encaja cuando tu app Remix incorpora una página visual real o un generador de correo electrónico los usuarios controlan, con tu propio almacenamiento y salida HTML. Para texto enriquecido en línea, un Un editor más ligero es suficiente; para composición a página completa con maquetación, estilo y exportación limpia, GrapesJS es la opción más fuerte, licenciada por MIT y autoalojada.
Próximos pasos
Véase el GrapesJS + React y Guías de GrapesJS + Next.js , consulta la Mercado GrapesJS, o empieza desde el GJS. Página principal del mercado.
Preguntas frecuentes
¿GrapesJS funciona con el renderizado de Remix server?
Sí — inicialízalo solo en useEffect para que funcione en el navegador. El
la ruta se renderiza en el servidor; Solo la instancia del editor es solo para el cliente.
¿Cómo guardo los datos de GrapesJS en Remix?
PUBLICA los datos del proyecto en una acción Remix y mantenlos en tu base de datos. Acciones Ejecuta en el servidor.
¿Cómo vuelvo a cargar contenido guardado?
Recupera el proyecto guardado de un cargador y llama
editor.loadProjectData(saved) después grapesjs.initde .
