INTEGRATION
Element Editor with Next.js
App Router and Pages Router both work. One caveat — the map engine needs window, so the editor renders client-side via dynamic import.
Install
npm install @element-editor/react maplibre-glElement needs React and React DOM 19.2 or newer, so it works with Next.js 15 and 16. Next.js 16 already ships React 19.2. A Next.js 15 app starts on React 19.1: upgrade it in the same step, or npm stops with a peer dependency conflict:
npm install @element-editor/react maplibre-gl react@^19.2 react-dom@^19.2App Router
Element relies on MapLibre GL JS (or Leaflet, on the /leaflet entry), which reads window during initialisation. That breaks server rendering. The fix is a one-line dynamic import with ssr: false:
// app/editor/page.tsx
'use client'
import dynamic from 'next/dynamic'
const ElementEditor = dynamic(
() => import('@element-editor/react').then((m) => m.ElementEditor),
{ ssr: false },
)
export default function EditorPage() {
return (
<div style={{ height: '80vh' }}>
<ElementEditor onChange={(data) => console.log(data)} />
</div>
)
}ssr: false is only allowed in a client component, so the file that calls dynamic needs 'use client'. In a larger app, put the dynamic call in a small client component and render it from a server page.
Pages Router
Same pattern, slightly different import:
// pages/editor.tsx
import dynamic from 'next/dynamic'
const ElementEditor = dynamic(
() => import('@element-editor/react').then((m) => m.ElementEditor),
{ ssr: false },
)
export default function EditorPage() {
return (
<div style={{ height: '80vh' }}>
<ElementEditor />
</div>
)
}Static export
Element works fine with output: 'export' — no server dependencies. The editor runs entirely in the browser; your static host delivers the built page and the CSS.
Server actions / route handlers
On the server side, you'll want to accept FeatureCollection shape from the client. The types are the only thing you need:
// app/api/features/route.ts
import type { FeatureCollection } from '@element-editor/react'
import { NextResponse } from 'next/server'
export async function POST(request: Request) {
const data: FeatureCollection = await request.json()
await saveToDatabase(data)
return NextResponse.json({ ok: true })
}Element types are usable server-side even though the library itself isn't — the type declarations have no runtime dependency on window.
Known gotchas
'use client'alone is not enough. It only marks the client boundary; Next.js still server-renders client components, so keepssr: false.- The stylesheet must be imported. Do it once, at the root layout:
import '@element-editor/react/style.css'(it includes MapLibre's CSS). Forgetting this produces a broken map with no obvious error. NotranspilePackagesor webpack config is needed. - Leaflet entry. For
@element-editor/react/leaflet, change the import path and stylesheet (@element-editor/react/leaflet/style.css), install@element-editor/adapter-leaflet leaflet react-leaflet(and@types/leafletfor TypeScript), and keepssr: false: Leaflet toucheswindowas soon as it's imported. - Content Security Policy. If your site sends one, the editor needs
worker-src blob:(MapLibre's and Element's workers) andstyle-src 'unsafe-inline'. See CSP. - Map containers need an explicit height. Next.js style resets sometimes collapse map heights to zero. Wrap in a div with
style={{ height: '...' }}or a class that sets a height.