---
title: "Leiho for Nuxt: visual editing on WordPress"
description: "A client plugin loads the bridge, a server route serves each page, and v-bind writes the attributes on your elements."
url: https://leiho.dev/frameworks/nuxt
lang: en
updated: 2026-11-04
---
Nuxt · Nuxt 3 and later

# Visual editing for Nuxt, on WordPress.

A client plugin loads the bridge, a server route serves each page, and v-bind writes the attributes on your elements.

1. 01
   ## Allow the editor to display your site
   Copy this into nuxt.config.ts.
   ```ts
   export default defineNuxtConfig({
     routeRules: {
       '/**': {
         headers: { 'Content-Security-Policy': "frame-ancestors 'self' https://cms.example.com" },
       },
     },
   });
   ```
2. 02
   ## Add the bridge
   Install the package, then create this client plugin.
   ```Terminal
   npm install leiho
   ```
   ```ts
   export default defineNuxtPlugin(() => {
     if (window.self === window.top) return;
     import('leiho').then(({ initLeihoBridge }) => {
       // Already on screen: don't hide the page.
       initLeihoBridge({ wpOrigin: 'https://cms.example.com', cloakTimeout: 0 });
     });
   });
   ```
3. 03
   ## Read the content
   Serve each page from a server route, then fetch it in your page.
   ```ts
   import { loadLeihoContent, indexLeihoContent } from 'leiho/content';
   
   export default defineEventHandler(async (event) => {
     const { path = '/' } = getQuery(event);
     const { data } = await loadLeihoContent({ url: 'https://cms.example.com/wp-json/leiho/v1/export' });
     const page = indexLeihoContent(data).byPath(String(path));
     if (!page) throw createError({ statusCode: 404 });
     return page;
   });
   ```
   ```vue
   <script setup lang="ts">
   const route = useRoute();
   const { data: page } = await useFetch('/api/page', { query: { path: route.path } });
   </script>
   
   <template>
     <main v-if="page" :data-wp-post="page.id">
       <h1 data-wp-field="_title">{{ page.title }}</h1>
     </main>
   </template>
   ```
   Serverless host with a read-only disk? Pass snapshot: false to loadLeihoContent.
4. 04
   ## Mark what editors can change
   Spread field() on each element editors should change. Always render the element, even when its value is empty.
   ```Vue
   <script setup>
   import { field, entry } from 'leiho';
   // field() writes data-wp-post + data-wp-field, entry() names a section.
   // Flexible content rows: block(page, 'sections', i, 'Label').
   </script>
   
   <template>
     <section v-bind="entry(page, 'Hero')">
       <h1 v-bind="field(page, 'hero_title')">{{ page.fields.hero_title }}</h1>
       <img v-bind="field(page, 'hero_image')" :src="page.fields.hero_image?.url" :alt="page.fields.hero_image?.alt">
       <article v-for="(card, i) in page.fields.cards" :key="i" v-bind="field(page, `cards.${i}.title`)">
         {{ card.title }}
       </article>
     </section>
   </template>
   ```

Replace https://cms.example.com with your WordPress address. In wp-admin, **Leiho → Get started** shows these steps with your address filled in, checks each one, and can give the whole setup to your coding agent as one prompt.

Other frameworks

- [**Next.js**](https://leiho.dev/frameworks/nextjs)
- [**Astro**](https://leiho.dev/frameworks/astro)
- [**SvelteKit**](https://leiho.dev/frameworks/sveltekit)
- [**React**](https://leiho.dev/frameworks/react)
- [**HTML, PHP and others**](https://leiho.dev/frameworks/html)

## Edit the real site.

Free and open source. Try it on a real WordPress, in your browser.

- [Try it in your browser](https://leiho.dev/try)
- [View on GitHub](https://github.com/pixelersagency/leiho)
