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-gl

Element 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.2

App 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 keep ssr: 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. No transpilePackages or 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/leaflet for TypeScript), and keep ssr: false: Leaflet touches window as 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) and style-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.

Ready to ship?