React · Also Remix and React Router
Visual editing for React, on WordPress.
React with Vite, Remix or React Router: one component next to your app, and field() on what editors should change.
- 01
Allow the editor to display your site
Set the header on your host, and in vite.config.ts for vite dev and vite preview.
public/_headers · Netlify · Cloudflare Pages /* Content-Security-Policy: frame-ancestors 'self' https://cms.example.comvercel.json · Vercel { "headers": [ { "source": "/(.*)", "headers": [ { "key": "Content-Security-Policy", "value": "frame-ancestors 'self' https://cms.example.com" } ] } ] }vite.config.ts · Dev server import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; const csp = "frame-ancestors 'self' https://cms.example.com"; export default defineConfig({ plugins: [react()], server: { headers: { 'Content-Security-Policy': csp } }, preview: { headers: { 'Content-Security-Policy': csp } }, }); - 02
Add the bridge
Install the package, then add the component next to your app in src/main.tsx.
Terminal npm install leihosrc/main.tsx import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import { LeihoBridge } from 'leiho/react'; import App from './App'; createRoot(document.getElementById('root')!).render( <StrictMode> <App /> <LeihoBridge wpOrigin="https://cms.example.com" /> </StrictMode> );Remix or React Router: put it in app/root.tsx.
- 03
Read the content
The app fetches one page at a time, from the browser.
src/usePage.ts import { useEffect, useState } from 'react'; export function usePage(path: string) { const [page, setPage] = useState<any>(null); useEffect(() => { const url = new URL('https://cms.example.com/wp-json/leiho/v1/content'); url.searchParams.set('path', path); let cancelled = false; fetch(url) .then((res) => res.json()) .then((items) => { if (!cancelled) setPage(items[0] ?? null); }); return () => { cancelled = true; }; }, [path]); return page; }Remix or React Router loaders: use loadLeihoContent from leiho/content instead.
- 04
Mark what editors can change
Spread field() on each element editors should change. Always render the element, even when its value is empty.
JSX 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'). <section {...entry(page, 'Hero')}> <h1 {...field(page, 'hero_title')}>{page.fields.hero_title}</h1> <img {...field(page, 'hero_image')} src={page.fields.hero_image?.url} alt={page.fields.hero_image?.alt} /> {page.fields.cards?.map((card, i) => ( <article key={i} {...field(page, `cards.${i}.title`)}>{card.title}</article> ))} </section>
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.
Edit the real site.
Free and open source. Try it on a real WordPress, in your browser.