All blocks

SEO Page Builder

Draggable map pin with breadcrumb, meta preview, and JSON-LD for full landing-page SEO workflows.

Live preview

SEO page builder
Move the marker to update breadcrumb, structured data, and meta for a property or locality landing page.
lng -9.13930lat 38.72230

Breadcrumb

No breadcrumb for this point.

Meta

No meta suggestions for this point.

JSON-LD

No JSON-LD for this point.

Install

$bunx shadcn@latest add https://mapbase.dev/r/seo-page-builder.json

The shadcn CLI copies the file(s) below into your tree, installs npm dependencies, and recursively pulls registry dependencies (including from cross-registry URLs).

npm dependencies

mapbasemaplibre-gllucide-react

Registry dependencies

cardhttps://www.mapcn.dev/r/map.jsonhttps://mapbase.dev/r/seo-breadcrumb-nav.jsonhttps://mapbase.dev/r/seo-meta-preview.jsonhttps://mapbase.dev/r/seo-json-ld.json

Source

components/mapbase/seo-page-builder.tsx
"use client"

import * as React from "react"
import { type CountryCode } from "mapbase"

import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"
import { SeoBreadcrumbNav } from "@/components/mapbase/seo-breadcrumb-nav"
import { SeoJsonLd } from "@/components/mapbase/seo-json-ld"
import { SeoMetaPreview } from "@/components/mapbase/seo-meta-preview"
import {
  Map,
  MapControls,
  MapMarker,
  MarkerContent,
  type MapViewport,
} from "@/components/ui/map"
import { cn } from "@/lib/utils"

export type SeoPageBuilderProps = {
  apiKey: string
  baseUrl?: string
  initialLng?: number
  initialLat?: number
  country?: CountryCode
  siteBaseUrl?: string
  siteName?: string
  debounceMs?: number
  className?: string
}

const DEFAULT_LNG = -9.1393
const DEFAULT_LAT = 38.7223

/**
 * Full SEO playground: drag a map pin, then render breadcrumb, JSON-LD, and
 * meta preview for the selected coordinates.
 */
export function SeoPageBuilder({
  apiKey,
  baseUrl,
  initialLng = DEFAULT_LNG,
  initialLat = DEFAULT_LAT,
  country,
  siteBaseUrl = "https://example.com",
  siteName = "Acme Realty",
  debounceMs = 350,
  className,
}: SeoPageBuilderProps) {
  const [lng, setLng] = React.useState(initialLng)
  const [lat, setLat] = React.useState(initialLat)
  const [viewport, setViewport] = React.useState<MapViewport>({
    center: [initialLng, initialLat],
    zoom: 12,
    bearing: 0,
    pitch: 0,
  })

  return (
    <Card className={cn("overflow-hidden", className)}>
      <CardHeader>
        <CardTitle>SEO page builder</CardTitle>
        <CardDescription>
          Move the marker to update breadcrumb, structured data, and meta for
          a property or locality landing page.
        </CardDescription>
      </CardHeader>
      <CardContent className="flex flex-col gap-6">
        <div className="h-56 overflow-hidden rounded-md border">
          <Map
            viewport={viewport}
            onViewportChange={setViewport}
            className="h-full w-full"
          >
            <MapControls position="bottom-right" />
            <MapMarker
              longitude={lng}
              latitude={lat}
              draggable
              onDragEnd={({ lng: nextLng, lat: nextLat }) => {
                setLng(nextLng)
                setLat(nextLat)
                setViewport((v) => ({ ...v, center: [nextLng, nextLat] }))
              }}
            >
              <MarkerContent />
            </MapMarker>
          </Map>
        </div>

        <div className="flex flex-wrap gap-2 text-xs font-mono text-muted-foreground">
          <span>lng {lng.toFixed(5)}</span>
          <span>lat {lat.toFixed(5)}</span>
        </div>

        <section className="flex flex-col gap-2">
          <h3 className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
            Breadcrumb
          </h3>
          <SeoBreadcrumbNav
            apiKey={apiKey}
            baseUrl={baseUrl}
            lng={lng}
            lat={lat}
            country={country}
            siteBaseUrl={siteBaseUrl}
            debounceMs={debounceMs}
          />
        </section>

        <section className="flex flex-col gap-2">
          <h3 className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
            Meta
          </h3>
          <SeoMetaPreview
            apiKey={apiKey}
            baseUrl={baseUrl}
            lng={lng}
            lat={lat}
            country={country}
            siteBaseUrl={siteBaseUrl}
            siteName={siteName}
            debounceMs={debounceMs}
          />
        </section>

        <section className="flex flex-col gap-2">
          <h3 className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
            JSON-LD
          </h3>
          <SeoJsonLd
            apiKey={apiKey}
            baseUrl={baseUrl}
            lng={lng}
            lat={lat}
            country={country}
            siteBaseUrl={siteBaseUrl}
            debounceMs={debounceMs}
            showPreview
          />
        </section>
      </CardContent>
    </Card>
  )
}