How to integrate GrapesJS into a Nuxt 3 app (complete guide 2026)

Embed GrapesJS in a Nuxt 3 app: mount it client-side with onMounted, save content to a Nitro server route, and export clean HTML/CSS.

DevFuture Development
DevFuture Development
May 29, 2026a month ago
6 min read4 views

Why GrapesJS fits Nuxt 3

GrapesJS needs the DOM, so in Nuxt 3 you initialise it in onMounted (client-only) while Nitro server routes handle saving. This guide mounts the editor, persists content, and exports HTML/CSS.

1. Mount the editor client-side

Create pages/editor.vue. Import GrapesJS dynamically inside onMounted so it never runs during SSR.

<template>
  <div>
    <button @click="save">Save</button>
    <div ref="el"><h1>Edit me in GrapesJS</h1></div>
  </div>
</template>

<script setup>
import 'grapesjs/dist/css/grapes.min.css';
const el = ref(null);
let editor;

onMounted(async () => {
  const grapesjs = (await import('grapesjs')).default;
  editor = grapesjs.init({
    container: el.value,
    height: '100vh',
    fromElement: true,
    storageManager: false,
  });
});

onBeforeUnmount(() => editor?.destroy());

async function save() {
  await $fetch('/api/page', {
    method: 'POST',
    body: {
      html: editor.getHtml(),
      css: editor.getCss(),
      project: editor.getProjectData(),
    },
  });
}
</script>

2. Add a Nitro server route

Create server/api/page.post.ts:

export default defineEventHandler(async (event) => {
  const data = await readBody(event);
  await savePage('home', data);   // your DB write
  return { status: 'ok' };
});

Add a matching server/api/page.get.ts to return the saved project, then call editor.loadProjectData(saved) after init to reopen it.

3. Export the markup

const html = editor.getHtml();
const css = editor.getCss();
// `<style>${css}</style>${html}`

Common pitfalls in Nuxt 3

Vue and JavaScript code on a screen
Import GrapesJS inside onMounted, never at module top level.

Nuxt's SSR is the usual culprit. A top-level import grapesjs from 'grapesjs' runs during server rendering and throws window is not defined — import it dynamically inside onMounted, or wrap the editor in <ClientOnly>. Persist through a Nitro server route in server/api with $fetch, keeping DB code on the server. If a dependency still complains during build, add it to build.transpile in nuxt.config. And call editor.destroy() in onBeforeUnmount so route changes don't leak editor instances.

Prerequisites

You'll need Node.js 18+ and a Nuxt 3 app. No Nuxt-specific GrapesJS package is required — the editor is browser-only and Nitro server routes handle saving. Familiarity with the Composition API, onMounted, and server/api routes is enough.

Add custom blocks to the editor

Register draggable blocks with the Block Manager after init (inside onMounted):

editor.BlockManager.add('hero', {
  label: 'Hero section',
  category: 'Sections',
  content: '<section class="hero"><h1>Headline</h1><p>Copy</p></section>',
});

Pull ready-made block libraries and presets from GJS.Market for a richer set.

Storage deep-dive: Nitro routes

Persist through Nitro server routes so DB code stays on the server:

// server/api/page.post.ts
export default defineEventHandler(async (event) => {
  const data = await readBody(event);
  await savePage('home', data);
  return { status: 'ok' };
});
// server/api/page.get.ts returns the saved project; then
// editor.loadProjectData(saved) after init reopens it.

Performance tips

Import GrapesJS dynamically inside onMounted so it stays out of the server render path and the main bundle, and call editor.destroy() in onBeforeUnmount so route changes don't leak instances. If a dependency complains at build time, add it to build.transpile in nuxt.config.

Security considerations

Authenticate and authorise the Nitro save route before writing. Sanitise stored markup on output if non-admins can edit. Validate the payload size so a large project can't exhaust memory.

Troubleshooting common errors

“window is not defined” means GrapesJS ran during SSR — import it dynamically inside onMounted or wrap the editor in <ClientOnly>. A build error about a dependency is usually fixed by adding it to build.transpile. An unstyled or blank canvas means the stylesheet didn't load or the ref wasn't ready.

When to use GrapesJS with Nuxt 3

GrapesJS fits when your Nuxt app embeds a real visual page or email builder your users control, with your own storage and HTML output. For inline rich text, a lighter editor is enough; for full-page composition with layout, styling, and clean export, GrapesJS is the stronger, MIT-licensed, self-hosted choice.

Next steps

See the related GrapesJS + Vue and GrapesJS + Svelte guides, browse the GrapesJS marketplace, or start from the GJS.Market home page.

FAQ

Does GrapesJS work with Nuxt 3 SSR?

Yes — initialise it in onMounted or wrap it in <ClientOnly> so it runs only in the browser.

How do I save GrapesJS content in Nuxt 3?

POST the project data with $fetch to a Nitro route in server/api.

Why does my build fail with "window is not defined"?

GrapesJS is being imported at module top level and running during SSR. Import it dynamically inside onMounted instead.

More tags:
Published May 29, 2026
Updated Jun 27, 2026
💚 Vue.js

Building with GrapesJS + Vue?

Find ready-made Vue-compatible plugins and presets on the marketplace — drop them in and skip the boilerplate.

Share this postTwitterFacebookLinkedIn
Published via
DevFuture Development
DevFuture Development
Visit shop →

More from DevFuture Development

Discover other insightful posts and stay updated with the latest content.

View all posts

Premium plugins from DevFuture Development

Hand-picked paid additions crafted by this creator.

Visit shop →