PageKit: el creador de sitios GrapesJS autoalojado, con el código fuente incluido. Obtener acceso anticipado

Next.js + GrapesJS

Creador de páginas Next.js con GrapesJS

Crea un generador de páginas visual de arrastrar y soltar dentro de tu aplicación Next.js con GrapesJS, componentes personalizados, datos persistentes de proyectos y tu propio flujo de trabajo de publicación.

Next.js App RouterReactArrastrar y soltarEdición visualHTML & CSSExtensible
your-app.com/editor
Next.js

Bloques

  • Hero
  • Cuadrícula de funciones
  • Precios
  • Formulario
  • Pie de página

Tu página

Sección Hero
Panel de bloques, lienzo, capas, gestor de estilos y dispositivos responsivos — el editor que te da el Surface GrapesJS antes de escribir una línea.

Impulsado por GrapesJS

Prueba con el editor

26k+

Estrellas en GitHub

1.4M+

Descargas de npm por mes

BSD-3-Clause

Licencia principal

100+

Plugins en GJS.Market

Cifras de GrapesJS verificadas el 2026-09-03 con el registro de npm y la API de GitHub. La biblioteca central es BSD-3-Clause; el wrapper oficial de React es MIT.

La cuestión de construir contra adoptar

Construye un constructor de páginas Next.js sin tener que construir el motor de edición

GrapesJS proporciona el motor de edición visual. Next.js proporciona la arquitectura de la aplicación que lo rodea. A continuación se lee la misma lista dos veces: de qué está hecho un editor y quién acaba construyendo cada parte una vez que adoptas GrapesJS.

  • Arrastrar y soltarNúcleo GrapesJS
  • LienzoNúcleo GrapesJS
  • Árbol de componentesNúcleo GrapesJS
  • Selección y hoverNúcleo GrapesJS
  • BloquesNúcleo GrapesJS
  • EstiloNúcleo GrapesJS
  • Edición responsivaNúcleo GrapesJS
  • Deshacer / volver a hacerNúcleo GrapesJS
  • Gestión de recursosNúcleo GrapesJS
  • SerializaciónNúcleo GrapesJS
  • ComandosNúcleo GrapesJS
  • Sistema de pluginsNúcleo GrapesJS
  • AlmacenamientoNúcleo GrapesJS
  • Proyectos de varias páginasPlugin o extensión
  • Biblioteca de plantillasPlugin o extensión
  • Usuarios y permisosTu app Next.js
  • PublicaciónTu app Next.js
¿Quién lo construye?Tu app Next.jsNúcleo GrapesJSPlugin o extensión

Dos de los diecisiete son tuyos. El resto o bien vienen con el editor o existen como un plugin que instalas.

Un creador de páginas parece una sola función y, de hecho, es un producto pequeño. La parte visible — un panel de bloques, un lienzo, una barra lateral de estilo — se sitúa sobre una docena de subsistemas que tienen que funcionar antes de que cualquiera de ellos resulte utilizable.

En lugar de reconstruir el motor de edición, usa GrapesJS y céntrate en tu producto.

Resultados

¿Qué puedes construir con un creador de páginas Next.js?

El mismo motor de edición respalda productos muy diferentes. Lo que cambia entre ellos son los bloques que registras, quién puede publicar y dónde va el resultado.

Prueba con un creador de páginas Next.js

Un editor GrapesJS real, ejecutándose en esta página. Arrastra un bloque desde la derecha, selecciona cualquier elemento del lienzo y cámbiale el estilo, o pasa el lienzo a un ancho de móvil: esta es la superficie que tendrían tus usuarios.

  • Drag & drop
  • Blocks
  • Layers
  • Style Manager
  • Responsive devices
  • Assets
  • Undo / redo

¿Qué ocurre cuando un usuario arrastra un bloque?

  1. Next.js application
  2. Client Component
  3. GrapesJS
  4. Project data
  5. Next.js API
  6. Database
  7. Publish

Tres responsabilidades, tres dueños.

Next.js se encarga del enrutamiento, autenticación, obtención de datos y la superficie desplegada. GrapesJS se encarga de todo lo que hay dentro del lienzo. Tu API es responsable de qué es un proyecto, quién puede editarlo y cuándo se publica. Cada sección de abajo es una de esas tres cajas abiertas.

Next.js se encarga de la aplicación. GrapesJS se encarga de la edición visual. Tu backend se encarga de la persistencia y la publicación. Nada en esa cadena requiere que renuncies a tu arquitectura existente.

Arquitectura

Cómo encaja GrapesJS en una aplicación Next.js

GrapesJS es una capa, no un framework. Renderiza un lienzo en un nodo DOM que le das y expone un API para todo lo que hay en ese lienzo. No tiene opinión sobre el enrutamiento, ni sobre tu base de datos, ni relación en tiempo de ejecución con el resto de tu app más allá del elemento que le asignó.

NEXT.JS

  • App Router
  • Authentication
  • Users
  • Permissions
  • API
  • Database
  • Billing
  • Publishing

CLIENT EDITOR

GRAPESJS

  • Canvas
  • Components
  • Blocks
  • Style Manager
  • Assets
  • Commands
  • Storage

Por eso la integración es pequeña. El editor es un widget del lado del cliente con un API rico — la misma forma que un editor de código o una biblioteca de gráficos, no un framework de aplicación competidor. Todo lo que hace que tu producto sea tuyo permanece en el lado Next.js de la línea.

Integración GrapesJS + React

GrapesJS no reemplaza Next.js. Añade la capa de edición visual que necesita tu aplicación.

Inicio rápido

Usa GrapesJS con Next.js App Router

GrapesJS necesita un navegador. Mide elementos, conecta oyentes y muta el DOM en el momento en que se inicializa, así que el editor pertenece dentro de un Client Component y su configuración dentro de un efecto. Esa es toda la restricción: todo lo demás es React ordinario.

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 />;
}

Por qué el ejemplo se ve así

'use client'

Marca el módulo como Client Component para que los ganchos React estén disponibles y el código se envía al navegador.

useRef, no useState

La instancia del editor no es datos de renderizado. Ponerlo en estado programa un re-renderizado en cada cambio sin ningún beneficio.

useEffect

Los efectos nunca se ejecutan durante el renderizado del servidor, así que la inicialización solo se garantiza una vez que el DOM exista.

editor.destroy()

React Strict Mode monta componentes dos veces en desarrollo. Sin limpieza tienes dos editores apilados en un solo nodo.

Eso es un editor funcional. El resto de esta página trata sobre las cuatro cosas que añades a continuación: dónde se almacena el proyecto, qué pueden arrastrar los usuarios, qué ocurre cuando publican y cuáles de esas no tienes que escribir tú mismo.

Enrutamiento

App Router vs Pages Router

Ambos funcionan. Difieren en dónde se traza el límite del cliente, y esa diferencia es la razón por la que muchos consejos de GrapesJS + Next.js en la web fallan en un proyecto moderno.
Recomendado

App Router

Un Client Component contiene el editor; la ruta alrededor de él permanece como Server Component. No se requiere importación dinámica, porque el editor ya está solo inicializado en el navegador.

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 />;
}
  • La directiva 'use client' marca el límite: todo lo que está debajo se envía al navegador.
  • El archivo de ruta permanece Server Component y puede esperar autenticación, parámetros y datos antes de renderizar el editor.
  • Un Client Component sigue prerenderizado en el servidor por defecto. Los efectos no, que es lo que mantiene el grapesjs.init() solo en el navegador.
  • next/dynamic con ssr: false es rechazado dentro de un Server Component — Next.js te dice que lo muevas a un Client Component.
Legacy

Pages Router

Cada página es un punto de entrada para el cliente, así que la receta habitual es una importación dinámica con el prerenderizado desactivado.

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 está permitido aquí y se salta completamente el pase de renderizado del servidor.
  • La opción de carga te da un marcador de posición mientras el chunk del editor se descarga.
  • El mismo componente de GrapesEditor funciona sin cambios: solo la forma en que se importa difiere.

Si estás migrando, no lleves el envoltorio dinámico() a través de él. En el App Router o falla directamente dentro de un Server Component o duplica el trabajo que ya hace el 'use client'.

Componentes del servidor

GrapesJS y React Server Components

Server Components es donde debe estar el trabajo alrededor del editor. Obtienen datos, ejecutan lógica del lado del servidor y proporcionan el shell de la aplicación. El Client Component inicializa GrapesJS, es dueño de su ciclo de vida y gestiona todas las interacciones del navegador. Los props cruzan el límite; la instancia del editor nunca lo hace.

Donde se asienta el límite

  1. Server Component
  2. Page / data
  3. Client Component
  4. 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} />;
}
  • Las comprobaciones de autenticación y permisos se realizan en el servidor, antes de que el paquete de edición merezca la pena descargar.
  • Los datos iniciales del proyecto se obtienen desde el lado del servidor y se transmiten como una propuesta sencilla y serializable.
  • La instancia del editor se queda dentro del Client Component. Nada de él es serializable, así que nada cruza el límite.
  • Server Actions puede llamarse desde el componente cliente para guardar guardados — son simplemente funciones en el lado cliente del borde.

Mantén el editor en el lado del cliente mientras usas Server Components para la aplicación que lo rodea.

SSR

¿Funciona GrapesJS con Next.js SSR?

Sí, pero el editor en sí debería inicializarse en el cliente porque depende del navegador APIs y del DOM.

La versión útil de esa respuesta es más específica, porque las dos mitades fallan de forma diferente.

Reproducido localmente

runtime
Node 20, no DOM
grapesjs
0.23.6
import
import('grapesjs') → resolves
init
grapesjs.init() → ReferenceError: document is not defined

2026-09-02

Importar la biblioteca en un entorno de ejecución sin DOM está bien. Llamar a init() no lo está. Así que la regla no es "mantener GrapesJS fuera del servidor" — es "mantener init() fuera de la ruta de renderizado", que un efecto ya garantiza.

  • La importación es segura. Agrupar GrapesJS en un módulo que el servidor también evalúe no se descarta.
  • La inicialización no lo es. grapesjs.init() lee documento, así que debe ejecutarse después de montar — que es exactamente lo que significa useEffect.
  • 'use client' no es lo mismo que "solo cliente". Client Components se prerenderizan en el servidor; el efecto es lo que no se ejecuta allí.
  • Por tanto, dynamic(..., { ssr: false }) es opcional en el App Router. Aprovecha para mantener el fragmento del editor fuera de la carga inicial, no para arreglar un fallo.
  • Renderizar una página publicada es un problema completamente distinto — véase más abajo. Esa página no necesita tiempo de ejecución de editor en absoluto, por lo que puede generarse estáticamente como cualquier otra cosa.
Autoría vs Servicio

Tu editor no tiene por qué ser tu página publicada

Esto es lo más útil que se puede hacer bien al principio y lo más fácil de equivocarse. El editor es un entorno de autoría. La página publicada puede usar tu propia arquitectura de renderizado Next.js — generación estática, streaming, revalidación, caché de bordes, todo.

Autoría

  1. GrapesJS
  2. Project data
  3. Database

Servicio

  1. Publish
  2. HTML + CSS
  3. Public page
Dos tuberías, un artefacto pasando entre ellas. Solo la primera necesita 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 nunca tiene que ejecutarse en una página que abre un visitante. Envía el editor a las pocas personas que editan, y manda HTML y CSS simples a todos los demás.

Persistencia

Guarda proyectos GrapesJS en tu aplicación Next.js

GrapesJS envia adaptadores de almacenamiento local y remoto y te permite registrar los tuyos propios. Un adaptador personalizado suele ser la opción correcta, porque pone cada lectura y escritura detrás de una ruta que controlas y puedes autenticar.

  1. GrapesJS
  2. Project data
  3. Next.js API
  4. Your backend
  5. Database
El editor nunca se comunica con tu base de datos. Se comunica con una ruta, que se comunica con lo que realmente usas.
components/GrapesEditor.tsx — almacenamientots
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/proyectos/[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 });
}
  • Carga: el load() del adaptador funciona en init y restaura el proyecto en el que el usuario estaba trabajando la última vez.
  • Guardar: store() recibe todo el proyecto como JSON. Devuelve una promesa rechazada para mostrar un fallo en el editor.
  • Guardado automático: el guardado automático con stepsBeforeSave agrupa los cambios para que no escribas en cada pulsación.
  • Borradores: mantén el proyecto borrador y la salida publicada en columnas separadas, para que la edición nunca altere lo que ven los visitantes.
  • Versionado: los datos del proyecto son un documento JSON — una tabla de versiones solo para añadir cuesta un inserto y compra rollback.
  • Publicación: un endpoint separado con su propia comprobación de permisos, no una bandera en la ruta de guardado.

Ejemplo: almacenar proyectos con 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() });
}

Esto implementa el límite loadProject y saveProject que importa la ruta anterior. Cambia el cuerpo por Prisma, Drizzle, Mongo, DynamoDB o una llamada REST a un backend existente y nada más en la página cambia.

Ejemplo: conectar almacenamiento de proyecto a un despliegue Vercel/Next.js

Publicar escribe HTML y CSS que tu ruta pública lee de vuelta. La revalidación, el almacenamiento en caché y la estrategia de renderizado siguen siendo preocupaciones habituales de Next.js: el editor no está en la ruta de solicitud.

Ver la ruta de la página publicada

Puedes conectar GrapesJS a cualquier backend o base de datos. Nada en el editor sabe ni le importa cuál elegiste.

Ampliando el lienzo

Crea componentes personalizados para tu creador de páginas Next.js

De fábrica, el lienzo ofrece a los usuarios HTML genérico. Los tipos de componentes personalizados son la forma en que el editor empieza a producir el marcado de tu producto — las mismas secciones que tu app Next.js ya renderiza, solo con las propiedades que decides exponer.

Registro de un tipo de componentets
// Your design system, expressed as something users can drop in,
// edit and style — but not break.
editor.Components.addType('pricing-table', {
  isComponent: (el) =>
    el.tagName === 'SECTION' && el.classList.contains('pricing'),

  model: {
    defaults: {
      tagName: 'section',
      classes: ['pricing'],
      // Structure the user cannot accidentally dismantle.
      droppable: false,
      // Fields exposed in the Settings panel.
      traits: [
        { name: 'plan', label: 'Plan name', type: 'text' },
        { name: 'price', label: 'Price', type: 'text' },
        { name: 'billing', label: 'Billing period', type: 'select',
          options: [
            { id: 'month', label: 'Monthly' },
            { id: 'year', label: 'Yearly' },
          ] },
      ],
      components: [
        { type: 'text', tagName: 'h3', content: 'Starter' },
        { type: 'text', tagName: 'p', content: '$19 / month' },
        { type: 'link', content: 'Choose plan', attributes: { href: '#' } },
      ],
    },
  },
});

Componentes personalizados comunes

  • Hero — titular, subtitular, fondo y una llamada a la acción
  • Tabla de precios — planes, precios y periodo de facturación como campos editables
  • Tarjeta de producto — vinculada a un ID real del producto en lugar de texto teleado
  • Cuadrícula de características — una disposición fija con un número variable de hijos
  • Formulario — campos que los usuarios pueden organizar, conectados por cable a tu endpoint de envío
  • Navegación — extraída de tus rutas para que los enlaces no se queden obsoletos
  • Componente de aplicación — un gráfico, un widget de reserva, cualquier cosa que tu SaaS ya envíe

Los componentes personalizados son la forma en que el editor empieza tu sistema de diseño y tu modelo de negocio. droppable: false impide que un usuario desmantele una estructura; traits decide exactamente qué propiedades pueden cambiar.

Construir una biblioteca de bloques

Los bloques son lo que aparece en el panel desde el que el usuario arrastra. Registrar uno cuesta unas líneas una vez que existe el tipo de componente.

Registro de un bloquets
// 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' },
});

Los bloques no son componentes

  • Un bloque es un punto de partida — una etiqueta, un icono y el contenido que inserta. Existe en el panel, no en la página.
  • Un componente es un elemento estructurado dentro del editor, con su propio modelo, traits y reglas. Existe en la página una vez insertado.
  • Un tipo de componente puede respaldar varios bloques (una tabla de precios de dos columnas y otra de tres columnas), y un bloque puede insertar un árbol completo de componentes.
  • Secciones reutilizables — Hero, características, testimonios, pie de página
  • Bloques de marketing — tablas de precios, muros de logotipos, llamadas a la acción
  • Bloques de disposición — columnas, separadores, contenedores
  • Formularios — contacto, inscripción, varios pasos
  • Bloques de comercio electrónico — cuadrículas de productos, resúmenes de carritos, indicaciones de pago
  • Bloques de aplicación — sea lo que haga tu producto y que una página debería poder mostrar

Gestionar imágenes y activos

El Asset Manager se encarga de la selección e inserción. Dónde están los archivos, quién puede subirlos y qué se permite es tu parte del contrato.

Asset Manager — subida personalizadats
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);
  },
},
  1. GrapesJS Asset Manager
  2. Next.js API
  3. Your storage
  4. CDN
Las subidas salen del navegador una vez y aterrizan en una ruta que tú escribiste.
  • Sube a través de tu propio gestor de ruta para que se apliquen la cookie de sesión, el límite de tasa y el registro de auditoría.
  • Permite URLs externas para equipos que ya alojan imágenes en otros sitios.
  • Precarga la biblioteca existente del usuario para que el selector se abra en sus assets, no en un panel vacío.
  • Valida el tipo MIME y el tamaño en el servidor. La entrada del archivo del editor es una sugerencia, no un control.
  • Sirve desde un CDN y guarda la URL de CDN en el proyecto, así las páginas publicadas nunca llegan a tu origen para el medio.
  • Paginar grandes bibliotecas — el panel de recursos intentará renderizar diez mil miniaturas encantado.
Modelo de datos

Datos del proyecto vs HTML y CSS

El editor produce dos cosas diferentes y no son intercambiables. Guardar la incorrecta es el error que convierte "editar tu página" en "empezar de nuevo".

Datos del proyecto

Un documento JSON que describe componentes, estilos, páginas y recursos. Este es el código fuente editable — el único artefacto que puede restaurar una sesión de edición exactamente como la dejó el usuario.

HTML

El contenido renderizado del lienzo. Perfecto para atender a los visitantes, con pérdida como fuente: los tipos de componentes, traits y el estado del editor han desaparecido.

CSS

La hoja de estilos que generó el editor, incluyendo las reglas para cada punto de interrupción. Se sirve junto con el HTML, regenerado cada vez que publicas.

Ambos artefactos, de un editorts
// 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);

Edición

  1. User edits
  2. Project data
  3. Database

Renderizado

  1. Project data
  2. HTML + CSS
  3. Preview / publish

Guarda los datos del proyecto para editar. Genera HTML/CSS cuando necesites publicar o renderizar contenido.

Flujo de trabajo

Del borrador a la página publicada

  1. 1
    Edición

    El usuario trabaja en el editor

    Los cambios se acumulan en los datos del proyecto. Nada de lo que ve un visitante se ha movido todavía.

  2. 2
    Guardar borrador

    Autosave escribe el proyecto

    Tu adaptador de almacenamiento publica el proyecto JSON en tu API con un rebote o un conteo de pasos.

  3. 3
    Vista previa

    Representa el borrador como lo haría un visitante

    Una ruta privada que genera HTML y CSS a partir del proyecto borrador — la misma ruta de código que usa la publicación, pero sin la escritura.

  4. 4
    Aprobación

    Quien sea el dueño de la página la firma

    Opcional, y merece la pena añadirlo la primera vez que alguien publica una tabla de precios rota.

  5. 5
    Publicar

    Congela la salida

    Genera HTML y CSS, desinfecta, almacena como una nueva versión y apunta el slug vivo hacia ella.

  6. 6
    Reversión

    Apunta la bala a una versión anterior

    Barato si cada publicación fuera un inserto en lugar de una actualización. Caro añadirlo después.

app/api/publicar/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}` });
}
Catálogo

Extiende tu creador de páginas Next.js con plugins

GrapesJS proporciona el motor de edición. Los plugins añaden la funcionalidad especializada que necesita un producto específico — y la mayoría de lo que un creador de páginas sigue faltando después de la primera semana ya existe como tal.

Explorar los plugins GrapesJS
Mercado

Plugins para una compilación Next.js

Listados en directo del catálogo GJS.Market, agrupados según la pregunta que un equipo de Next.js haga tras montar el editor.

Control de alcance

No construyas cada característica tú mismo

Los plugins no eliminan el trabajo de desarrollo. Cambian el tipo de trabajo, y esa diferencia se acumula a lo largo de la vida útil del producto.
Escrito internamente

Hazlo tú mismo

Cada capacidad que escribas se convierte en una línea permanente.

    Del catálogo

    Instala un plugin

    Alguien ya ha resuelto la parte genérica de tu editor.

      Usa el núcleo GrapesJS para el editor y añade solo las capacidades que tu producto realmente necesite.

      La decisión

      ¿Construir un creador de páginas Next.js desde cero o usar GrapesJS?

      Capacidad por capacidad, lo que te vas a inscribir para escribir. "Extensible" significa que la biblioteca proporciona la interfaz y el predeterminado, y espera que lo apuntes a tu propio backend.

      CapacidadConstrúeteGrapesJS
      LienzoConstruirlo túIncluido
      Arrastrar y soltarConstruirlo túIncluido
      ComponentesConstruirlo túIncluido
      BloquesConstruirlo túIncluido
      EstiloConstruirlo túIncluido
      Edición responsivaConstruirlo túIncluido
      ActivosConstruirlo túExtensible
      AlmacenamientoConstruirlo túExtensible
      MandosConstruirlo túIncluido
      PluginsConstruir un ecosistemaArquitectura de plugins

      Verificado en comparación con el GrapesJS API publicado en 2026-09-02.

      Next.js te da el marco de la aplicación. GrapesJS te da el motor de edición visual.

      Productos comerciales

      Crea un Constructor de Páginas SaaS con Next.js

      Un creador de páginas SaaS es tu aplicación con un editor dentro. Casi todo lo que lo convierte en un negocio — cuentas, equipos, límites, facturación, dominios — es trabajo Next.js que harías de todas formas. El editor es una vía.

      NEXT.JS

      • Authentication
      • Organizations
      • Users
      • Permissions
      • Billing
      • Editor

      GRAPESJS

      PROJECT API

      • Database
      • Publishing
      • Multiusuario: varias personas editando diferentes proyectos y, finalmente, el mismo.
      • Permisos: quién puede editar, quién puede publicar, quién solo puede mirar. Impuesto en el servidor.
      • Organizaciones y equipos: los proyectos pertenecen a una cuenta, no a una persona.
      • Facturación: límites de planes expresados como conteos de proyectos, páginas, sedes o dominios publicados.
      • Publicación: el paso en el que tu producto asume la responsabilidad de lo que se publica.
      • Etiqueta blanca: tus paneles, tus iconos, tus colores — el editor no debe parecer amontonado.

      El editor es una vía en tu solicitud. Todo lo que lo rodea es lo que realmente estás vendiendo.

      Antes de que envíes

      Consideraciones de rendimiento y seguridad

      Dos listas que merecen la pena leer antes de que el primer usuario real abra el editor, no después.

      Rendimiento

      Mantén al editor fuera del camino rápido

      GrapesJS es una biblioteca importante para el cliente. Eso está bien en la ruta de autoría y es caro en el resto de sitios.

      • Carga perezosamente GrapesJS para que su fragmento se obtenga con la ruta del editor, no con el shell de la aplicación.
      • Inicializa solo cuando realmente necesites el editor — no en un panel que simplemente enlaza a él.
      • Mantén la instancia del editor en una referencia, fuera del estado React.
      • Evita volver a renderizar el componente que posee el editor; el ciclo de vida del GrapesJS no es del React.
      • Plugins pesados de carga perezosa en lugar de registrarlos todos en init.
      • Paginar colecciones grandes de recursos en lugar de cargar toda la biblioteca en el panel.

      El editor es una herramienta de autoría. Normalmente no necesitas el tiempo completo de ejecución del editor en cada página publicada.

      Seguridad

      Trata la salida del editor como entrada del usuario

      Porque lo es. Todo lo que el lienzo produjo llegó a través de la red desde un navegador que no controlas.

      • HTML generado o proporcionado por el usuario antes de almacenarlo, dondequiera que se muestre como marcado.
      • Valida los activos subidos en el lado del servidor: tipo, tamaño y qué estás dispuesto a devolver MIME.
      • Autentifica cada punto final de almacenamiento. Un ID de proyecto en una URL no es autorización.
      • Hacer cumplir permisos en el lado del servidor; un botón oculto de publicar es una preferencia de UI, no un control.
      • Valida la forma de los datos del proyecto antes de escribirlos, y de nuevo antes de renderizar desde ellos.
      • Protege los endpoints de publicación por separado del guardado: tienen radios de explosión diferentes.
      • Establece un Content Security Policy para las rutas que generan el marcado publicado.

      Nunca confíes en el estado del editor del lado del cliente. El editor es una conveniencia para el autor, no un límite.

      Resolución de problemas

      Errores comunes de Next.js + GrapesJS

      Casi todos los fallos de integración reportados para esta pila son uno de los siguientes siete.

      • Error

        Inicialización de GrapesJS dentro de un Server Component

        Server Components no tiene ganchos, ni efectos ni DOM. La importación puede resolverse, pero no hay dónde montar el editor.

        Solución

        Usa un Client Component — pon 'use client' en la parte superior del módulo que posee el editor.

      • Error

        Inicialización antes de que exista el DOM

        Llamar a init() durante el renderizado, o contra una referencia que sigue siendo nula, falla porque el elemento contenedor aún no se ha creado.

        Solución

        Inicializa después de la montura, dentro de useEffect, y vigila que la referencia esté presente.

      • Error

        Ejecutar GrapesJS durante SSR

        Cualquier llamada a init() que llegue al servidor lanza el documento ReferenceError: no está definido. La importación por sí sola es inofensiva.

        Solución

        Mantén la inicialización del editor en el lado del cliente. Un efecto es suficiente; las importaciones dinámicas son una optimización, no la solución.

      • Error

        Poner la instancia del editor en estado React

        El editor es un objeto grande y mutable que cambia constantemente. Almacenarlo en programas de estado se vuelve a renderizar sin lograr nada.

        Solución

        Usa un ref. Guarda el estado de las cosas que realmente renderiza el UI, como un indicador de guardado.

      • Error

        Volver a renderizar el editor innecesariamente

        Un re-renderizado padre que recrea los objetos o vuelve a montar el contenedor desmonta y reconstruye todo el lienzo.

        Solución

        Mantén el ciclo de vida del GrapesJS separado del renderizado normal del React — monta una vez y luego pasa por su propio API.

      • Error

        Almacenar solo HTML

        HTML es la salida, no la fuente. Reabrir un proyecto desde HTML pierde tipos de componentes, traits y estado del editor, así que la siguiente edición comienza desde una página aplanada.

        Solución

        Persiste los datos del proyecto si los usuarios necesitan seguir editando. Genera HTML al publicar.

      • Error

        Ejecutando GrapesJS en todas las páginas públicas

        Enviar el runtime del editor a los visitantes que no pueden editar nada cuesta tamaño del paquete, memoria y Core Web Vitals sin devolución.

        Solución

        Separar el tiempo de ejecución del editor del contenido publicado — renderiza las páginas publicadas como HTML y CSS simples.

      Hoja de ruta

      Empieza poco a poco y luego escala

      Tres miras, cada una sumándose a la anterior. La mayoría de los equipos se pasan en la primera y se sorprenden con la tercera.

      1. 1Semana uno

        MVP

        Demuestra que la experiencia de edición es la adecuada antes de construir algo alrededor de ella.

        • GrapesJS montado en un componente cliente
        • Un puñado de bloques básicos
        • Almacenamiento local o de un solo proyecto
        • Publicación sencilla en una sola vía
        Empieza aquí
      2. 2Primeros usuarios reales

        Producción

        Todo lo que convierte una demo en algo en lo que un cliente pueda confiar.

        • Autenticación en cada ruta de editor
        • Almacenamiento en el backend detrás de tu propio API
        • Subidas de activos y una biblioteca multimedia
        • Componentes personalizados que se ajustan a tu sistema de diseño
        • Permisos aplicados en el lado del servidor
        • Versionado y un flujo de trabajo de publicación
        Consulta la sección de almacenamiento
      3. 3Comercial

        SaaS

        La capa de producto alrededor del editor, que es por lo que realmente cobras.

        • Organizaciones y equipos
        • Límites de facturación y planes
        • Editor de marca blanca UI
        • Un conjunto de plugins seleccionado para tus usuarios
        • Una biblioteca de plantillas
        • Analítica en páginas publicadas
        Creador de páginas SaaS

      El camino desde el prototipo hasta el producto comercial pasa por tu aplicación, no por el editor.

      FAQ

      Preguntas frecuentes

      ¿Puedo usar GrapesJS con Next.js?

      Sí. GrapesJS es una biblioteca del lado del cliente que se monta en un elemento DOM, así que se ejecuta dentro de cualquier aplicación React, incluida Next.js. Instala grapesjs, inicialízalo en un efecto dentro de un Client Component y destrúyelo al desmontar.

      ¿Funciona GrapesJS con el Next.js App Router?

      Sí, y el App Router es la integración recomendada. Pon 'use client' en la parte superior del componente que posee el editor e inicializa GrapesJS en useEffect. El archivo de ruta en sí puede seguir siendo Server Component.

      ¿Funciona GrapesJS con React Server Components?

      Funciona junto a ellos. El editor en sí no puede ser un Server Component — necesita ganchos, efectos y el DOM. Server Components se encarga de la autenticación, la obtención de datos y el page shell, luego pasa los props serializables al Client Component que posee el editor.

      ¿GrapesJS soporta Next.js SSR?

      El componente que envuelve el editor puede renderizarse en el servidor; el propio editor debe inicializarse en el navegador. grapesjs.init() lee documentos y añade un tiempo de ejecución Node, así que mantén esa llamada dentro de un efecto. Las páginas publicadas no necesitan tiempo de ejecución del editor y pueden generarse estáticamente.

      ¿Por qué GrapesJS necesita un Client Component?

      Porque interactúa directamente con el DOM — midiendo elementos, conectando oyentes y renderizando un lienzo iframe — y porque necesita ganchos React para gestionar su ciclo de vida. Ninguno de los dos está disponible en un Server Component.

      ¿Cómo inicializo GrapesJS en Next.js?

      Crea un Client Component con una referencia en un div contenedor, llama a grapesjs.init({ contenedor }) dentro de useEffect con un array de dependencias vacío, mantén el editor devuelto en una segunda referencia y llama a editor.destroy() en la función de limpieza.

      ¿Debería usar dynamic(..., { ssr: false })?

      En el Pages Router, sí — es la forma estándar de saltarse el prerenderizado. En el App Router es opcional y no se puede usar dentro de un Server Component: el Next.js rechaza el ssr: false allí y te pide que lo muevas a un Client Component. Úsalo en el App Router solo para evitar que el bloque del editor esté fuera de la carga útil inicial.

      ¿Cómo guardo proyectos GrapesJS en Next.js?

      Registra un adaptador de almacenamiento personalizado con editor.Storage.add(), cuyo load() y store() llaman a un gestor de rutas Next.js. La ruta autentica la solicitud y lee o escribe el proyecto JSON en tu base de datos. Activa el guardado automático con stepsBeforeSave para que las escrituras se agrupen.

      ¿Puedo usar Supabase con GrapesJS y Next.js?

      Sí, y hay un ejemplo en esta página. Supabase es una implementación del límite carga/guardado — Postgres, MySQL, Mongo, S3 o un API REST existente funcionan igual, porque el editor solo habla con tu ruta.

      ¿Puedo crear un creador de páginas SaaS con Next.js y GrapesJS?

      Sí. GrapesJS es BSD-3-Clause licenciado y autoalojado, sin tarifa por asiento ni servicio alojado en el bucle, así que puedes integrarlo en un producto comercial. El trabajo es principalmente la capa SaaS — organizaciones, permisos, facturación, publicación — que es trabajo ordinario de Next.js.

      ¿Puedo crear bloques personalizados?

      Sí. editor.Blocks.add() registra un bloque con una etiqueta, una categoría, un icono y el contenido que inserta. Los bloques pueden insertar HTML en bruto o instanciar uno de tus propios tipos de componentes.

      ¿Puedo crear componentes personalizados?

      Sí. editor.Components.addType() define un tipo de componente con su propio modelo, traits, reglas y estructura hija: el mecanismo para exponer tu sistema de diseño dentro del lienzo mientras evita que los usuarios lo rompan.

      ¿Puedo exportar HTML y CSS?

      Sí. editor.getHtml() y editor.getCss() devuelven la salida de lienzo en cualquier momento, y getProjectData() devuelve la fuente editable de JSON. Guarda los datos del proyecto para editar; genera HTML y CSS cuando publiques.

      ¿Puedo usar plugins GrapesJS con Next.js?

      Sí. Los plugins son funciones que reciben la instancia del editor, por lo que se registran igual en Next.js que en cualquier otro sitio — a través de la opción de plugins en init, o llamando al editor API desde tu manejador onEditor.

      ¿Puedo construir un editor CMS con Next.js?

      Sí, y es uno de los usos más comunes. Mapea los datos del proyecto GrapesJS en tu modelo de contenido existente, restringe el conjunto de bloques a los componentes que tus plantillas Next.js puedan renderizar y deja que los editores trabajen visualmente sin tocar el repositorio.

      ¿Puedo autoalojar un creador de páginas Next.js?

      Sí. GrapesJS es un paquete npm sin backend alojado, servidor de licencias ni telemetría, así que todo el constructor — editor, almacenamiento y páginas publicadas — se ejecuta donde se ejecute tu aplicación Next.js.

      Construye tu creador de páginas Next.js con GrapesJS

      Usa Next.js para la arquitectura de tu aplicación y GrapesJS para el motor de edición visual. Empieza con el editor central y amplía tu producto con los plugins, bloques e integraciones que necesites.

      Construye la aplicación con Next.js. Añade edición visual con GrapesJS. Extiende la aplicación con GJS.Market.