---
title: "Quick start: Nuxt"
description: "Register the Nuxt module, provide EditorPorts and mount the simple and the advanced editor in a Nuxt app."
canonical_url: "https://skaile-ai.github.io/forge-editor/getting-started/quick-start-nuxt"
---
# Quick start: Nuxt

> Register the Nuxt module, provide EditorPorts and mount the simple and the advanced editor in a Nuxt app.

Install the peers next to the package (`vue`, `@nuxt/ui`, `@tiptap/core`, `@tiptap/pm`, `@tiptap/vue-3`, `yjs`, `@hocuspocus/provider`, `tailwindcss`, `@iconify-json/lucide`; see [Installation](https://skaile-ai.github.io/forge-editor/getting-started/installation)), then follow the steps. Inside the repository declare `"@skaile/forge-editor": "workspace:*"` instead of the npm version.

<steps level="2">

## Register the module after `@nuxt/ui`

```ts
// nuxt.config.ts
export default defineNuxtConfig({
  ssr: false, // TipTap renders on the client only
  modules: ['@nuxt/ui', '@skaile/forge-editor/nuxt'],
  css: ['~/assets/main.css'],
})
```

The module patches Nuxt UI's generated `ui.css` template and throws if that template is missing, so `@nuxt/ui` must come first. It adds every feature stylesheet to `css` (`themes.css` last), puts the package into `build.transpile` and Nitro's `externals.inline`, enables Nitro websockets, installs the client plugin `editor-context-nuxt-ui.client.ts` (`createEditor({ kit: nuxtUiKit(), backend: createHttpBackend({ fetch: $fetch, probe: true }) })`; the module has no `kit` option, the Vue binding is Nuxt UI) and scans the Nitro routes. It registers no component or composable.

Module options live under the config key `tiptapEditor`:

```ts
// nuxt.config.ts
export default defineNuxtConfig({
  ssr: false, // TipTap renders on the client only
  modules: ['@nuxt/ui', '@skaile/forge-editor/nuxt'],
  tiptapEditor: { ai: { /* optional; presets (Model presets), roles (the default Model preset per Model role), rateLimit, transcription: see AI configuration */ }, collab: { authenticateUpgrade: false /* true: refuse an unauthenticated /collab upgrade with HTTP 401 (cookie hosts only) */ } },
})
```

The `ai` options are described in [AI configuration](https://skaile-ai.github.io/forge-editor/guides/ai-configuration).

## Add the stylesheet

```css
/* app/assets/main.css */
@import "tailwindcss";
@import "@nuxt/ui";
```

## Mount the simple editor

Wrap the app in `<UApp>` (`app/app.vue`: `<UApp><NuxtPage /></UApp>`) and mount an editor on a page (the simple editor needs no backend):

```vue
<script setup lang="ts">
import { SimpleEditor } from '@skaile/forge-editor/vue/simple'
import { ref } from 'vue'
const content = ref()
</script>

<template>
  <SimpleEditor v-model="content" />
</template>
```

## Register `EditorPorts`

The module's API routes call the host's `EditorPorts` (identity first, then one port per capability: `documents`, `collabState`, `directory`, `assets`, `settings`, `themes`, `models`, `comments`). Hand them to the module with `provideEditorServer({ ports })` in a Nitro plugin; the module installs the editor server at the first request, and without provided ports the client logs „Einstellungen konnten nicht geladen werden“ and every route answers 500. A route whose port you leave out answers 501. These minimal ports keep everything in memory and let any visitor in (replace `authenticate` and the storage with yours; [Server ports](https://skaile-ai.github.io/forge-editor/guides/editor-ports) lists every member, the routes that call it and the required results):

```ts [docs/snippets/minimal-ports.ts]
// server/plugins/editor-adapter.ts
import type { JSONContent } from '@tiptap/core'
import { type AssetStore } from '@skaile/forge-editor/server'
import { provideEditorServer } from '@skaile/forge-editor/server/nitro'

export default defineNitroPlugin(() => {
  const contents = new Map<string, JSONContent>()
  const states = new Map<string, Uint8Array>()
  const assets = new Map<string, Parameters<AssetStore['storeAsset']>[1]>()
  provideEditorServer({
    ports: {
      // `null` = 401 on API routes; on /collab the socket is closed with 4401 (HTTP 401 at the upgrade with `collab.authenticateUpgrade`)
      identity: { authenticate: async () => ({ id: 'demo', username: 'demo' }) },
      documents: {
        loadContent: async id => contents.get(id) ?? null,
        storeContent: async (id, content) => { contents.set(id, content) },
        listContents: async () => [...contents].map(([id, content]) => ({ id, content })),
      },
      collabState: {
        loadState: async id => states.get(id) ?? null,
        storeState: async (id, state) => { states.set(id, state) },
        removeState: async (id) => { states.delete(id) },
      },
      directory: { listUsers: async () => [] },
      assets: {
        storeAsset: async (id, asset) => { assets.set(`${id}/${asset.name}`, asset) },
        loadAsset: async (id, name) => {
          const asset = assets.get(`${id}/${name}`)
          if (!asset) return null
          const { data, ...record } = asset
          return { record, data }
        },
      },
      // Omit `settings`: settings stay in the browser (GET answers 204).
      // Omit `themes`: the themes routes answer 501 and the standard look stays.
    },
  })
})
```

## Mount the advanced editor

`app.vue` must wrap pages in `<UApp>` (overlays, toasts, tooltips). The toolbar is a sibling that receives the editor from `ready`:

```vue
<script setup lang="ts">
import type { Editor, JSONContent } from '@tiptap/vue-3'
import { AdvancedEditor } from '@skaile/forge-editor/vue/advanced'
import { EditorToolbar } from '@skaile/forge-editor/vue'
import { ref, shallowRef } from 'vue'

const content = ref<JSONContent>()
const editor = shallowRef<Editor>()
</script>

<template>
  <EditorToolbar :editor="editor" />
  <AdvancedEditor v-model="content" @ready="editor = $event" />
</template>
```

Without a `collaboration` prop the editor is standalone (JSON `v-model`, own undo history). Collaboration is a mode fixed at mount: pass `:collaboration="{ ydoc, provider, user }"` and connect the provider to `/collab/:id` ([Collaboration](https://skaile-ai.github.io/forge-editor/guides/collaboration)).

`AdvancedEditor` pulls in every feature. For a smaller bundle use `SimpleEditor` or compose your own list ([Feature composition](https://skaile-ai.github.io/forge-editor/guides/composition)). For the simple editor in plain Vue (no Nuxt, no backend) follow the [plain Vue quick start](https://skaile-ai.github.io/forge-editor/getting-started/quick-start-vue).

</steps>

## Next steps

The advanced editor, every adapter method, collaboration and AI are covered in the guides: [Server ports](https://skaile-ai.github.io/forge-editor/guides/editor-ports), [Routes and authentication](https://skaile-ai.github.io/forge-editor/guides/routes-and-auth), [Collaboration](https://skaile-ai.github.io/forge-editor/guides/collaboration) and [AI configuration](https://skaile-ai.github.io/forge-editor/guides/ai-configuration). The [tutorial](https://skaile-ai.github.io/forge-editor/tutorial/overview) builds an editor from the simple editor up.


## Sitemap

See the full [sitemap](https://skaile-ai.github.io/forge-editor/sitemap.md) for all pages.
