Draggable map pin with breadcrumb, meta preview, and JSON-LD for full landing-page SEO workflows.
No breadcrumb for this point.
No meta suggestions for this point.
No JSON-LD for this point.
bunx shadcn@latest add https://mapbase.dev/r/seo-page-builder.jsonThe shadcn CLI copies the file(s) below into your tree, installs npm dependencies, and recursively pulls registry dependencies (including from cross-registry URLs).
"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>
)
}