Collection Integration
This guide shows how to embed maps in Astro collection item posts using content collection schemas and map builder utilities. Whether you’re creating a travel blog, restaurant reviews, or hiking guides, maplibre-yaml provides simple patterns for adding geographic context.
Overview
Section titled “Overview”The collection item integration approach uses three key concepts:
- Content Collection Schemas - Pre-built Zod schemas that add location/region/route fields to your collection item frontmatter
- Map Builder Utilities - Functions that convert simple geographic data into full map configurations
- Global Configuration - Site-wide map style settings that individual posts can inherit
Quick Start
Section titled “Quick Start”1. Define Your Collection Schema
Section titled “1. Define Your Collection Schema”Choose the schema that matches your content type:
import { defineCollection } from "astro:content";import { glob } from "astro/loaders";import { getCollectionItemWithLocationSchema } from "@maplibre-yaml/astro/utils";
export const collections = { posts: defineCollection({ loader: glob({ pattern: "**/*.md", base: "./src/collections/posts" }), schema: getCollectionItemWithLocationSchema(), }),};A glob() loader works on Astro 5, 6 and 7 as written. On Astro 4 it needs
experimental: { contentLayer: true }, the config file at
src/content/config.ts, and a base outside src/content/ (see
Supported versions). Loader entries
have an id (the file name without its extension) and render with render()
from astro:content; the legacy type: "content" API (post.slug,
post.render()) is gone in Astro 6.
2. Add Location to Your Posts
Section titled “2. Add Location to Your Posts”---title: "My Trip to Paris"pubDate: 2024-03-15location: coordinates: [2.3522, 48.8566] name: "Paris, France" zoom: 12---We arrived in Paris on a crisp spring morning...3. Render the Map
Section titled “3. Render the Map”---import { getCollection, render } from 'astro:content';import { Map, buildPointMapConfig } from '@maplibre-yaml/astro';import BaseLayout from '../../layouts/BaseLayout.astro';
export async function getStaticPaths() { const posts = await getCollection('posts'); return posts.map(post => ({ params: { slug: post.id }, props: { post } }));}
const { post } = Astro.props;const { Content } = await render(post);
// Build map config if location existsconst mapConfig = post.data.location ? buildPointMapConfig({ location: post.data.location, mapStyle: 'https://demotiles.maplibre.org/style.json' }) : null;---
<BaseLayout title={post.data.title}> <article> <h1>{post.data.title}</h1>
{mapConfig && ( <Map config={mapConfig} height="300px" /> )}
<Content /> </article></BaseLayout>Schema Options
Section titled “Schema Options”Single Location Posts
Section titled “Single Location Posts”Best for: Restaurant reviews, place spotlights, single-destination content.
import { getCollectionItemWithLocationSchema } from "@maplibre-yaml/astro/utils";
const schema = getCollectionItemWithLocationSchema();Frontmatter example:
---title: "Best Coffee in Brooklyn"pubDate: 2024-03-15location: coordinates: [-73.9857, 40.6892] name: "Devoción Coffee" zoom: 16 markerColor: "#8B4513"---Multiple Locations Posts
Section titled “Multiple Locations Posts”Best for: Travel itineraries, city guides, comparison posts.
import { getCollectionItemWithLocationsSchema } from "@maplibre-yaml/astro/utils";
const schema = getCollectionItemWithLocationsSchema();Frontmatter example:
---title: "10 Days in Japan"pubDate: 2024-03-15locations: - coordinates: [139.6917, 35.6895] name: "Tokyo" description: "Days 1-4" - coordinates: [135.7681, 35.0116] name: "Kyoto" description: "Days 5-7" - coordinates: [135.5023, 34.6937] name: "Osaka" description: "Days 8-10"---Region/Area Posts
Section titled “Region/Area Posts”Best for: Neighborhood guides, property coverage areas, territorial content.
import { getCollectionItemWithRegionSchema } from "@maplibre-yaml/astro/utils";
const schema = getCollectionItemWithRegionSchema();Frontmatter example:
---title: "Guide to Brooklyn Heights"pubDate: 2024-03-15region: name: "Brooklyn Heights" coordinates: - [ [-73.998, 40.698], [-73.985, 40.698], [-73.985, 40.692], [-73.998, 40.692], [-73.998, 40.698], ] fillColor: "#3388ff" fillOpacity: 0.3---Route/Trail Posts
Section titled “Route/Trail Posts”Best for: Hiking guides, road trips, cycling routes.
import { getCollectionItemWithRouteSchema } from "@maplibre-yaml/astro/utils";
const schema = getCollectionItemWithRouteSchema();Frontmatter example:
---title: "Pacific Coast Highway Road Trip"pubDate: 2024-03-15route: name: "SF to LA via PCH" coordinates: - [-122.4194, 37.7749] # San Francisco - [-122.0322, 36.9741] # Santa Cruz - [-121.8947, 36.6002] # Monterey - [-120.6596, 35.2828] # San Luis Obispo - [-119.6982, 34.4208] # Santa Barbara - [-118.2437, 34.0522] # Los Angeles color: "#e74c3c" width: 4---Flexible Schema
Section titled “Flexible Schema”For collection items with mixed content types, use the comprehensive schema:
import { z } from "astro/zod";import { getCollectionItemWithGeoSchema } from "@maplibre-yaml/astro/utils";
const schema = getCollectionItemWithGeoSchema({ // Add your custom fields author: z.string(), category: z.enum(["travel", "hiking", "food", "city-guide"]), tags: z.array(z.string()).default([]),});Map Builder Functions
Section titled “Map Builder Functions”buildPointMapConfig
Section titled “buildPointMapConfig”Creates a map centered on a single location with a marker:
import { buildPointMapConfig } from "@maplibre-yaml/astro";
const config = buildPointMapConfig({ location: { coordinates: [-74.006, 40.7128], name: "New York City", description: "The Big Apple", }, mapStyle: "https://demotiles.maplibre.org/style.json", zoom: 12, // optional, defaults to location.zoom or 12 interactive: true, // optional, defaults to true});buildMultiPointMapConfig
Section titled “buildMultiPointMapConfig”Creates a map showing multiple locations with automatic bounds fitting:
import { buildMultiPointMapConfig } from "@maplibre-yaml/astro";
const config = buildMultiPointMapConfig({ locations: [ { coordinates: [-74.006, 40.7128], name: "NYC" }, { coordinates: [-118.2437, 34.0522], name: "LA" }, { coordinates: [-87.6298, 41.8781], name: "Chicago" }, ], mapStyle: "https://demotiles.maplibre.org/style.json", padding: 50, // pixels of padding around bounds});buildPolygonMapConfig
Section titled “buildPolygonMapConfig”Creates a map displaying a polygon region:
import { buildPolygonMapConfig } from "@maplibre-yaml/astro";
const config = buildPolygonMapConfig({ region: { coordinates: [ [ [-74.0, 40.7], [-73.9, 40.7], [-73.9, 40.8], [-74.0, 40.8], [-74.0, 40.7], // Close the polygon ], ], name: "Manhattan", fillColor: "#ff6b6b", fillOpacity: 0.4, }, mapStyle: "https://demotiles.maplibre.org/style.json",});buildRouteMapConfig
Section titled “buildRouteMapConfig”Creates a map showing a line/route with start/end markers:
import { buildRouteMapConfig } from "@maplibre-yaml/astro";
const config = buildRouteMapConfig({ route: { coordinates: [ [-122.4194, 37.7749], [-121.8947, 36.6002], [-118.2437, 34.0522], ], name: "California Road Trip", color: "#e74c3c", width: 4, }, mapStyle: "https://demotiles.maplibre.org/style.json",});Global Configuration Pattern
Section titled “Global Configuration Pattern”Instead of specifying mapStyle on every map, create a shared configuration.
For a validated, YAML-backed version that every builder accepts as its
second argument, see loadGlobalMapConfig in
Astro Integration → Global Configuration.
A plain object works too:
export const globalMapConfig = { mapStyle: "https://api.maptiler.com/maps/streets/style.json?key=YOUR_KEY", // Add any other defaults you want to share};Then use it in your pages:
---import { Map, buildPointMapConfig } from '@maplibre-yaml/astro';import { globalMapConfig } from '../lib/map-config';
const { location } = Astro.props.post.data;
const mapConfig = location ? buildPointMapConfig({ location, mapStyle: globalMapConfig.mapStyle }) : null;---
{mapConfig && <Map config={mapConfig} height="300px" />}Complete Blog Template Example
Section titled “Complete Blog Template Example”Here’s a full example of a collection item post template with conditional map rendering:
---import { getCollection, render } from 'astro:content';import { Map, buildPointMapConfig, buildMultiPointMapConfig, buildPolygonMapConfig, buildRouteMapConfig} from '@maplibre-yaml/astro';import BaseLayout from '../../layouts/BaseLayout.astro';
export async function getStaticPaths() { const posts = await getCollection('posts'); return posts.map(post => ({ params: { slug: post.id }, props: { post } }));}
const { post } = Astro.props;const { Content } = await render(post);const { location, locations, region, route } = post.data;
const MAP_STYLE = 'https://demotiles.maplibre.org/style.json';
// Build appropriate map config based on available datafunction getMapConfig() { if (location) { return buildPointMapConfig({ location, mapStyle: MAP_STYLE }); } if (locations && locations.length > 0) { return buildMultiPointMapConfig({ locations, mapStyle: MAP_STYLE }); } if (region) { return buildPolygonMapConfig({ region, mapStyle: MAP_STYLE }); } if (route) { return buildRouteMapConfig({ route, mapStyle: MAP_STYLE }); } return null;}
const mapConfig = getMapConfig();---
<BaseLayout title={post.data.title}> <article class="post"> <header> <h1>{post.data.title}</h1> <time datetime={post.data.pubDate.toISOString()}> {post.data.pubDate.toLocaleDateString()} </time> </header>
{mapConfig && ( <div class="post-map"> <Map config={mapConfig} height="350px" /> </div> )}
<div class="post-content"> <Content /> </div> </article></BaseLayout>
<style> .post-map { margin: 2rem 0; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); }</style>Utility Functions
Section titled “Utility Functions”calculateCenter
Section titled “calculateCenter”Calculate the center point of multiple coordinates:
import { calculateCenter } from "@maplibre-yaml/astro";
const center = calculateCenter([ [-74.006, 40.7128], [-73.935, 40.73], [-73.867, 40.752],]);// Returns approximately [-73.936, 40.732]calculateBounds
Section titled “calculateBounds”Calculate the bounding box of coordinates:
import { calculateBounds } from "@maplibre-yaml/astro";
const bounds = calculateBounds([ [-74.006, 40.7128], [-73.867, 40.752],]);// Returns [[-74.006, 40.7128], [-73.867, 40.752]]Tips & Best Practices
Section titled “Tips & Best Practices”Keep Maps Simple
Section titled “Keep Maps Simple”For blog posts, simpler maps are often better. A single marker showing “where this happened” is more effective than a complex multi-layer visualization.
Use Appropriate Zoom Levels
Section titled “Use Appropriate Zoom Levels”- City overview: zoom 10-12
- Neighborhood: zoom 13-15
- Specific building/location: zoom 16-18
Consider Non-Interactive Maps
Section titled “Consider Non-Interactive Maps”For purely illustrative maps, disable interactivity to reduce visual noise:
const config = buildPointMapConfig({ location, mapStyle: MAP_STYLE, interactive: false,});Lazy Load Maps Below the Fold
Section titled “Lazy Load Maps Below the Fold”If your map isn’t immediately visible, consider lazy loading:
{mapConfig && ( <div class="post-map" data-map-config={JSON.stringify(mapConfig)}> <!-- Placeholder, hydrate with JS when visible --> </div>)}