Use src Attribute (Recommended)
Place YAML files in /public/configs/ and use <ml-map src="/configs/map.yaml">. Simplest and most maintainable approach.
Astro is a natural fit for maplibre-yaml - both embrace configuration-driven approaches. This guide covers integrating maps into Astro sites, from simple page embeds to reusable components.
Get a map in your Astro site in 5 minutes.
Install dependencies
pnpm add @maplibre-yaml/core maplibre-glCreate a map configuration file
Create public/configs/hero-map.yaml:
type: mapid: hero-mapconfig: center: [-74.006, 40.7128] zoom: 12 mapStyle: "https://demotiles.maplibre.org/style.json"layers: - id: marker type: circle source: type: geojson data: type: FeatureCollection features: - type: Feature geometry: type: Point coordinates: [-74.006, 40.7128] properties: name: "New York City" paint: circle-radius: 12 circle-color: "#3b82f6" circle-stroke-width: 2 circle-stroke-color: "#ffffff"Create a Map component
Create src/components/Map.astro:
---interface Props { src: string; height?: string; class?: string;}
const { src, height = '400px', class: className } = Astro.props;---
<ml-map src={src} style={`height: ${height}; display: block;`} class={className}/>
<script> import '@maplibre-yaml/core/register'; import 'maplibre-gl/dist/maplibre-gl.css';</script>
<style> ml-map { width: 100%; border-radius: 8px; overflow: hidden; }</style>Use in a page
---import Layout from '../layouts/Layout.astro';import Map from '../components/Map.astro';---
<Layout title="Home"> <h1>Welcome</h1> <Map src="/configs/hero-map.yaml" height="500px" /></Layout>For Astro projects, we recommend using the dedicated @maplibre-yaml/astro package instead of the core web components directly. This package provides:
/public for dynamic configspnpm add @maplibre-yaml/astro @maplibre-yaml/core maplibre-glThe Map component is the simplest way to add a map to your Astro site:
Load YAML from /public directory:
---import { Map } from '@maplibre-yaml/astro';---
<Map src="/configs/map.yaml" height="500px" class="my-map"/>Place your YAML file in /public/configs/map.yaml:
type: mapid: my-mapconfig: center: [-74.006, 40.7128] zoom: 12 mapStyle: "https://demotiles.maplibre.org/style.json"layers: - id: points type: circle source: type: geojson url: "https://example.com/data.geojson" paint: circle-radius: 6 circle-color: "#007cbf"Load and validate YAML at build time:
---import { Map, loadMapConfig } from '@maplibre-yaml/astro';
// Loaded at build time - validates YAML and catches errors earlyconst config = await loadMapConfig('./src/configs/map.yaml');---
<Map config={config} height="600px" style="border: 2px solid #e2e8f0; border-radius: 8px;"/>Benefits of build-time loading:
interface MapProps { // Data source (one required) src?: string; // Path to YAML file in /public config?: MapBlock; // Pre-loaded configuration
// Styling height?: string; // Container height (default: "400px") class?: string; // CSS class name style?: string; // Inline styles}Examples:
<!-- Minimal --><Map src="/maps/simple.yaml" />
<!-- With custom height --><Map src="/maps/dashboard.yaml" height="100vh" />
<!-- With styling --><Map src="/maps/styled.yaml" height="500px" class="shadow-lg rounded-lg" style="border: 1px solid #ccc;"/>
<!-- Build-time loaded -->---import { Map, loadMapConfig } from '@maplibre-yaml/astro';const earthquakes = await loadMapConfig('./src/maps/earthquakes.yaml');---<Map config={earthquakes} height="600px" />The package provides utilities for loading YAML configurations:
Load and validate a map configuration from a file:
---import { loadMapConfig, Map } from '@maplibre-yaml/astro';
try { const config = await loadMapConfig('./src/configs/my-map.yaml'); // config is fully typed as MapBlock} catch (error) { console.error('Failed to load map config:', error);}---
<Map config={config} />Load multiple configurations at once:
---import { loadFromGlob, Map } from '@maplibre-yaml/astro';import { YAMLParser } from '@maplibre-yaml/core';
const configFiles = import.meta.glob('./src/configs/*.yaml', { as: 'raw' });const maps = await loadFromGlob( configFiles, (yaml) => YAMLParser.safeParseMapBlock(yaml));---
<div class="map-gallery"> {maps.map(({ path, config }) => ( <div> <h2>{config.id}</h2> <Map config={config} height="400px" /> </div> ))}</div>Use Astro Content Collections for managing map configurations:
1. Define collection schema:
import { defineCollection } from 'astro:content';import { getMapSchema } from '@maplibre-yaml/astro';
export const collections = { maps: defineCollection({ type: 'data', schema: getMapSchema() })};2. Add YAML files:
type: mapid: earthquake-mapconfig: center: [-120, 35] zoom: 6 mapStyle: "https://demotiles.maplibre.org/style.json"layers: - id: earthquakes type: circle source: type: geojson url: "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_week.geojson" paint: circle-radius: 8 circle-color: "#ff4444"3. Query and use in pages:
---import { getCollection } from 'astro:content';import { Map } from '@maplibre-yaml/astro';
const maps = await getCollection('maps');const earthquakeMap = maps.find(m => m.id === 'earthquake-map');---
<Map config={earthquakeMap.data} height="600px" />The Map component displays user-friendly errors when configurations fail to load or validate:
<!-- Invalid YAML or network error will show error message --><Map src="/configs/broken.yaml" />Error display includes:
You can also handle errors programmatically with build-time loading:
---import { loadMapConfig, YAMLLoadError } from '@maplibre-yaml/astro';
let config;try { config = await loadMapConfig('./src/configs/map.yaml');} catch (error) { if (error instanceof YAMLLoadError) { console.error('Validation errors:'); error.errors.forEach(e => { console.error(` ${e.path}: ${e.message}`); }); } throw error; // Fail build on invalid config}---
<Map config={config} />Add custom metadata to your map configurations:
import { defineCollection } from 'astro:content';import { extendSchema, getMapSchema } from '@maplibre-yaml/astro';import { z } from 'zod';
export const collections = { maps: defineCollection({ type: 'data', schema: extendSchema(getMapSchema(), { author: z.string(), publishDate: z.date(), tags: z.array(z.string()), featured: z.boolean().default(false) }) })};author: "Jane Doe"publishDate: 2024-01-15tags: ["earthquakes", "geoscience"]featured: true
type: mapid: featured-mapconfig: center: [0, 0] zoom: 2 mapStyle: "https://demotiles.maplibre.org/style.json"---import { getCollection } from 'astro:content';
const featuredMaps = await getCollection('maps', ({ data }) => data.featured);---
{featuredMaps.map(entry => ( <div> <h2>{entry.data.id}</h2> <p>By {entry.data.author} on {entry.data.publishDate}</p> <Map config={entry.data} /> </div>))}The FullPageMap component creates a full-viewport map with built-in controls and an optional legend. It’s perfect for landing pages, dashboards, or dedicated map views.
src (runtime) and config (build-time) loading---import { FullPageMap } from '@maplibre-yaml/astro';---<FullPageMap src="/configs/world-map.yaml" />---import { FullPageMap, loadMapConfig } from '@maplibre-yaml/astro';const config = await loadMapConfig('./src/configs/world-map.yaml');---<FullPageMap config={config} />interface FullPageMapProps { // Map source (one required) src?: string; // Path to YAML file in /public config?: MapBlock; // Pre-loaded configuration object
// Control options showControls?: boolean; // Show zoom/reset controls (default: true) showLegend?: boolean; // Show auto-generated legend (default: false) legendPosition?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'; // Legend position (default: 'top-right')
// Styling (optional - component is fixed viewport by default) class?: string; // Additional CSS classes style?: string; // Additional inline styles}---import { FullPageMap } from '@maplibre-yaml/astro';---<!DOCTYPE html><html> <head> <title>Interactive Map Dashboard</title> </head> <body style="margin: 0; padding: 0;"> <FullPageMap src="/configs/dashboard-map.yaml" showControls showLegend legendPosition="bottom-left" /> </body></html><FullPageMap src="/configs/presentation-map.yaml" showControls={false}/><FullPageMap src="/configs/branded-map.yaml" showLegend legendPosition="top-right" style="z-index: 100;" class="dashboard-map"/>---import { FullPageMap, loadMapConfig } from '@maplibre-yaml/astro';
const mapConfig = await loadMapConfig('./src/configs/geospatial-data.yaml');---<FullPageMap config={mapConfig} showControls showLegend legendPosition="bottom-right"/>When showLegend is enabled, the component automatically generates a legend based on your map’s layers:
The legend automatically:
The built-in controls provide:
Controls are accessible with ARIA labels and keyboard navigation.
The Chapter component displays individual narrative sections within scrollytelling experiences. It’s typically used internally by the Scrollytelling component but can be used independently for custom implementations.
interface ChapterProps { id: string; // Unique chapter identifier (required) title: string; // Chapter title (required) description?: string; // HTML description content image?: string; // Image URL (absolute or relative) video?: string; // Video URL (absolute or relative) alignment?: 'left' | 'right' | 'center' | 'full'; // Content position (default: 'center') hidden?: boolean; // Hide content for map-only chapters isActive?: boolean; // Active state (controlled by parent) theme?: 'light' | 'dark'; // Visual theme (default: 'light')}---import { Chapter } from '@maplibre-yaml/astro';---<Chapter id="intro" title="Introduction" description="<p>Welcome to our story about climate data.</p>" alignment="center"/><Chapter id="hero" title="The Journey Begins" description="<p>Exploring the landscape of our data.</p>" image="/images/hero-landscape.jpg" alignment="left" theme="dark"/><Chapter id="demo" title="How It Works" description="<p>Watch this short demonstration.</p>" video="/videos/tutorial.mp4" alignment="center"/><Chapter id="transition" title="Transition View" hidden/><Chapter id="finale" title="Conclusion" description="<p>Thank you for following this journey through the data.</p>" alignment="full" theme="dark"/>Light theme (default):
Dark theme:
The isActive prop controls visual emphasis:
This is typically managed by the parent Scrollytelling component based on scroll position.
<section> with role="region"aria-labelledby for each chapteralt textThe Scrollytelling component creates immersive narrative experiences where the map transitions between different views as users scroll through story chapters. Perfect for data journalism, interactive reports, and guided data tours.
interface ScrollytellingProps { src?: string; // Path to YAML config file (in public directory) config?: ScrollytellingBlock; // Pre-loaded scrollytelling configuration class?: string; // Additional CSS class for container debug?: boolean; // Debug mode - shows chapter boundaries}---import { Scrollytelling } from '@maplibre-yaml/astro';---<Scrollytelling src="/stories/earthquake-history.yaml" />---import { Scrollytelling, loadScrollytellingConfig } from '@maplibre-yaml/astro';const story = await loadScrollytellingConfig('./src/stories/climate-change.yaml');---<Scrollytelling config={story} />A complete scrollytelling story includes base map configuration, data sources, layers, and chapters:
version: "1.0"id: earthquake-storytheme: dark # 'light' or 'dark'showMarkers: true # Show chapter navigation dotsmarkerColor: "#3FB1CE" # Custom marker color
# Base map configurationconfig: style: "https://demotiles.maplibre.org/style.json" center: [-118.2437, 34.0522] zoom: 9 pitch: 0 bearing: 0
# Data sourcessources: earthquakes: type: geojson data: "/data/earthquakes.geojson"
# Map layerslayers: - id: earthquake-circles type: circle source: earthquakes paint: circle-radius: 8 circle-color: "#ff0000"
# Story chapterschapters: - id: intro title: "Los Angeles Earthquakes" description: "<p>Exploring seismic activity in Southern California.</p>" center: [-118.2437, 34.0522] zoom: 9 alignment: center
- id: detail title: "Major Fault Lines" description: "<p>The San Andreas Fault runs through this region.</p>" image: "/images/fault-lines.jpg" center: [-118.0, 34.0] zoom: 11 pitch: 45 bearing: 30 animation: flyTo speed: 0.8 alignment: left layers: show: [earthquake-circles] hide: []
- id: conclusion title: "Preparing for the Future" description: "<p>Understanding earthquake patterns helps us prepare.</p>" center: [-118.2437, 34.0522] zoom: 9 alignment: full
footer: "<p>Data source: USGS</p><p>© 2024</p>"---import { Scrollytelling } from '@maplibre-yaml/astro';---<Scrollytelling src="/stories/city-tour.yaml" />---import { Scrollytelling, loadScrollytellingConfig } from '@maplibre-yaml/astro';const story = await loadScrollytellingConfig('./src/stories/development.yaml');---<Scrollytelling config={story} debug /><Scrollytelling src="/stories/brand-story.yaml" class="branded-scrollytelling"/>
<style> .branded-scrollytelling { font-family: 'Custom Font', sans-serif; }</style>Each chapter supports extensive configuration options:
Position & Camera:
center: [longitude, latitude] coordinateszoom: Zoom level (0-22)pitch: Camera tilt (0-60 degrees)bearing: Camera rotation (0-360 degrees)Animation:
animation: “flyTo” (smooth arc), “easeTo” (direct), or “jumpTo” (instant)speed: Animation speed (0.1-2.0, default 0.6)curve: Fly curve intensity (0.1-2.0, default 1.0)Content:
title: Chapter heading (required)description: HTML contentimage: Image URL for visual contextvideo: Video URL with inline playeralignment: “left”, “right”, “center”, or “full”hidden: Hide content for map-only transitionsInteractivity:
layers.show: Array of layer IDs to make visiblelayers.hide: Array of layer IDs to hideonChapterEnter: Actions to execute when entering chapteronChapterExit: Actions to execute when leaving chapterExecute MapLibre actions when users enter or exit chapters:
chapters: - id: filtered-view title: "Major Earthquakes Only" center: [-118.2, 34.0] zoom: 10 onChapterEnter: - action: setFilter layer: earthquake-circles filter: [">=", "magnitude", 5.0] - action: setPaintProperty layer: earthquake-circles property: circle-color value: "#ff0000" onChapterExit: - action: setFilter layer: earthquake-circles filter: nullAvailable Actions:
setFilter: Filter features in a layersetPaintProperty: Change layer paint propertiessetLayoutProperty: Change layer layout propertiesWhen showMarkers: true, navigation dots appear on the right side of the viewport:
markerColorLight theme (default):
Dark theme:
Enable debug prop to see:
<Scrollytelling src="/stories/test.yaml" debug />The component uses IntersectionObserver with these settings:
jumpTo for instant transitions on slower devices---import { Scrollytelling, loadScrollytellingConfig } from '@maplibre-yaml/astro';
const story = await loadScrollytellingConfig('./src/stories/earthquakes.yaml');---<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Earthquake History Story</title> </head> <body> <Scrollytelling config={story} /> </body></html>
<style is:global> body { margin: 0; padding: 0; font-family: system-ui, sans-serif; }</style>The simplest pattern - let the web component handle everything. Place YAML files in the /public directory and reference them with the src attribute.
File structure:
public/├── configs/│ ├── hero-map.yaml│ ├── contact-map.yaml│ └── locations-map.yamlsrc/├── components/│ └── Map.astro└── pages/ └── index.astroMap.astro:
---interface Props { src: string; height?: string;}
const { src, height = '400px' } = Astro.props;---
<ml-map src={src} style={`height: ${height}; display: block;`} />
<script> import '@maplibre-yaml/core/register'; import 'maplibre-gl/dist/maplibre-gl.css';</script>Usage:
<Map src="/configs/hero-map.yaml" /><Map src="/configs/contact-map.yaml" height="300px" />Benefits:
For projects that want build-time validation, you can parse YAML during the build:
File structure:
src/├── components/│ └── Map.astro├── configs/│ ├── hero-map.yaml│ ├── contact-map.yaml│ └── locations-map.yaml└── pages/ └── index.astroMap.astro:
---import { YAMLParser } from '@maplibre-yaml/core';
interface Props { configPath: string; height?: string;}
const { configPath, height = '400px' } = Astro.props;
// Import all YAML files at build timeconst yamlFiles = import.meta.glob('/src/configs/**/*.yaml', { as: 'raw', eager: true});
const yamlContent = yamlFiles[configPath];
if (!yamlContent) { const available = Object.keys(yamlFiles).join(', '); throw new Error(`Config not found: ${configPath}. Available: ${available}`);}
const result = YAMLParser.safeParseMapBlock(yamlContent);
if (!result.success) { console.error('YAML parse errors:', result.errors); throw new Error(`Invalid config ${configPath}: ${result.errors[0].message}`);}
const configJson = JSON.stringify(result.data);---
<ml-map style={`height: ${height}; display: block;`} config={configJson} />
<script> import '@maplibre-yaml/core/register'; import 'maplibre-gl/dist/maplibre-gl.css';</script>Usage:
<Map configPath="/src/configs/hero-map.yaml" /><Map configPath="/src/configs/contact-map.yaml" height="300px" />Benefits:
Tradeoffs:
For simple, one-off maps, you can inline configuration. However, this approach has significant drawbacks and is generally not recommended.
InlineMap.astro:
---import { YAMLParser } from '@maplibre-yaml/core';
interface Props { yaml: string; height?: string;}
const { yaml, height = '400px' } = Astro.props;
const result = YAMLParser.safeParseMapBlock(yaml);
if (!result.success) { console.error('Parse errors:', result.errors);}
const configJson = result.success ? JSON.stringify(result.data) : null;const error = result.success ? null : result.errors;---
{error ? ( <div class="map-error"> <strong>Configuration Error</strong> <pre>{JSON.stringify(error, null, 2)}</pre> </div>) : ( <ml-map style={`height: ${height}; display: block;`} config={configJson} />)}
<script> import '@maplibre-yaml/core/register'; import 'maplibre-gl/dist/maplibre-gl.css';</script>
<style> .map-error { padding: 20px; background: #fef2f2; border: 1px solid #fecaca; border-radius: 8px; color: #dc2626; } .map-error pre { margin-top: 10px; font-size: 12px; overflow-x: auto; }</style>Usage in page:
---import InlineMap from '../components/InlineMap.astro';
// ⚠️ MUST start content immediately after backtickconst simpleMapYaml = `type: mapid: simple-mapconfig: center: [-74.006, 40.7128] zoom: 12 mapStyle: "https://demotiles.maplibre.org/style.json"layers: []`;---
<InlineMap yaml={simpleMapYaml} height="300px" />In MDX files (like documentation), use the src attribute pattern for the cleanest approach:
guide.mdx:
---title: Map Guide---
import Map from "../../components/Map.astro";
## Live Earthquake Data
Here's a map showing recent earthquakes:
<Map src="/configs/earthquake-map.yaml" height="400px" />
The data refreshes from the USGS API.public/configs/earthquake-map.yaml:
type: mapid: earthquake-democonfig: center: [-120, 37] zoom: 4 mapStyle: "https://demotiles.maplibre.org/style.json"layers: - id: quakes type: circle source: type: geojson url: "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_week.geojson" paint: circle-radius: 6 circle-color: "#ef4444"A full-featured component with events and controls using the src pattern:
InteractiveMap.astro:
---interface Props { src: string; height?: string; showControls?: boolean; onLayerClick?: string; // JavaScript function name to call}
const { src, height = '400px', showControls = false, onLayerClick} = Astro.props;
const mapId = `map-${Math.random().toString(36).slice(2, 9)}`;---
<div class="interactive-map" id={mapId}> {showControls && ( <div class="map-controls"> <button class="control-btn" data-action="zoom-in" title="Zoom In">+</button> <button class="control-btn" data-action="zoom-out" title="Zoom Out">−</button> <button class="control-btn" data-action="reset" title="Reset View">⌂</button> </div> )}
<ml-map src={src} style={`height: ${height}; display: block;`} data-click-handler={onLayerClick} />
<div class="map-status"></div></div>
<script> import '@maplibre-yaml/core/register'; import 'maplibre-gl/dist/maplibre-gl.css';
// Initialize all interactive maps document.querySelectorAll('.interactive-map').forEach(container => { const mapEl = container.querySelector('ml-map'); const statusEl = container.querySelector('.map-status'); const controls = container.querySelector('.map-controls');
if (!mapEl) return;
let initialCenter: [number, number] | null = null; let initialZoom: number | null = null;
// Status updates mapEl.addEventListener('ml-map:load', () => { const map = mapEl.getMap(); initialCenter = map.getCenter().toArray() as [number, number]; initialZoom = map.getZoom(); statusEl.textContent = 'Map ready'; statusEl.className = 'map-status ready'; });
mapEl.addEventListener('ml-map:layer-loading', (e: CustomEvent) => { statusEl.textContent = `Loading ${e.detail.layerId}...`; statusEl.className = 'map-status loading'; });
mapEl.addEventListener('ml-map:layer-loaded', (e: CustomEvent) => { statusEl.textContent = `${e.detail.featureCount} features loaded`; statusEl.className = 'map-status ready'; });
mapEl.addEventListener('ml-map:error', (e: CustomEvent) => { statusEl.textContent = `Error: ${e.detail.error.message}`; statusEl.className = 'map-status error'; });
// Custom click handler const clickHandler = mapEl.dataset.clickHandler; if (clickHandler && typeof window[clickHandler] === 'function') { mapEl.addEventListener('ml-map:layer-click', (e: CustomEvent) => { window[clickHandler](e.detail); }); }
// Control buttons if (controls) { controls.addEventListener('click', (e) => { const btn = (e.target as HTMLElement).closest('[data-action]'); if (!btn) return;
const map = mapEl.getMap(); if (!map) return;
const action = btn.dataset.action;
switch (action) { case 'zoom-in': map.zoomIn(); break; case 'zoom-out': map.zoomOut(); break; case 'reset': if (initialCenter && initialZoom !== null) { map.flyTo({ center: initialCenter, zoom: initialZoom }); } break; } }); } });</script>
<style> .interactive-map { position: relative; border-radius: 8px; overflow: hidden; border: 1px solid #e5e7eb; }
.map-controls { position: absolute; top: 10px; right: 10px; z-index: 10; display: flex; flex-direction: column; gap: 4px; }
.control-btn { width: 32px; height: 32px; border: none; background: white; border-radius: 4px; font-size: 18px; cursor: pointer; box-shadow: 0 2px 4px rgba(0,0,0,0.1); display: flex; align-items: center; justify-content: center; }
.control-btn:hover { background: #f3f4f6; }
.map-status { position: absolute; bottom: 10px; left: 10px; padding: 4px 8px; background: rgba(255,255,255,0.9); border-radius: 4px; font-size: 12px; z-index: 10; }
.map-status.loading { color: #d97706; } .map-status.ready { color: #059669; } .map-status.error { color: #dc2626; }</style>For sites with many maps that need build-time validation, you can use Astro Content Collections. Note that this is more complex than the recommended src attribute pattern:
src/content/config.ts:
import { defineCollection, z } from "astro:content";
const maps = defineCollection({ type: "data", schema: z.object({ type: z.literal("map"), id: z.string(), config: z.object({ center: z.tuple([z.number(), z.number()]), zoom: z.number(), mapStyle: z.string(), pitch: z.number().optional(), bearing: z.number().optional(), }), layers: z.array(z.any()), }),});
export const collections = { maps };src/content/maps/headquarters.yaml:
type: mapid: headquartersconfig: center: [-122.4194, 37.7749] zoom: 15 mapStyle: "https://demotiles.maplibre.org/style.json"layers: - id: office type: circle source: type: geojson data: type: Feature geometry: type: Point coordinates: [-122.4194, 37.7749] properties: name: "Headquarters" paint: circle-radius: 12 circle-color: "#3b82f6"CollectionMap.astro:
---import { getEntry } from 'astro:content';
interface Props { slug: string; height?: string;}
const { slug, height = '400px' } = Astro.props;
const mapEntry = await getEntry('maps', slug);
if (!mapEntry) { throw new Error(`Map not found: ${slug}`);}
// Content collection already validates against schemaconst configJson = JSON.stringify(mapEntry.data);---
<ml-map style={`height: ${height}; display: block;`} config={configJson} />
<script> import '@maplibre-yaml/core/register'; import 'maplibre-gl/dist/maplibre-gl.css';</script>Usage:
<CollectionMap slug="headquarters" height="500px" />Cause: YAML indentation was destroyed by template literal processing, or the file couldn’t be loaded.
Solutions:
Use the src attribute pattern (recommended)
<Map src="/configs/my-map.yaml" />Place your YAML file in public/configs/my-map.yaml
Check browser console for loading errors
The web component will log errors if it can’t fetch or parse the YAML file.
Verify file path is correct
Files in /public are served from the root, so /public/configs/map.yaml becomes src="/configs/map.yaml"
Symptoms: Nothing visible, or map is 0px tall.
Cause: CSS height not set properly.
Solution:
<ml-map src="/configs/map.yaml" style="height: 400px; display: block;" />Both height and display: block are required.
Symptoms: Error: Config not found or network 404 error
Cause: Incorrect path or file not in public directory.
Solutions:
/public directory (not /src)src="/configs/map.yaml" for /public/configs/map.yamlSymptoms: Changes to YAML files in /public don’t appear immediately.
Cause: Browser caching or dev server needs refresh.
Workaround:
/public are served statically, so they update immediately without rebuildimport.meta.globSolution: Add to src/env.d.ts:
/// <reference types="astro/client" />
declare module "*.yaml" { const content: string; export default content;}Symptoms: <ml-map> renders as unknown element.
Cause: Registration script didn’t run.
Solution: Ensure script is in component:
<script> import '@maplibre-yaml/core/register'; import 'maplibre-gl/dist/maplibre-gl.css';</script>The script must be inside the component, not in the frontmatter.
Use src Attribute (Recommended)
Place YAML files in /public/configs/ and use <ml-map src="/configs/map.yaml">. Simplest and most maintainable approach.
Set Explicit Dimensions
Always set height and display: block on <ml-map> elements.
Keep Config Separate
Avoid inline YAML in components. External files are easier to edit and maintain.
Use Build-Time Validation (Optional)
For critical applications, parse YAML at build time (Pattern 2) to catch errors early.
<ml-map>