Writing on GIS, maps, and shipping from zero to one.

Writing on GIS, MapLibre, vector tiles, PMTiles, and building with Vue.js, from an engineer taking geospatial products from zero to one.

Subscribe via RSS
css

TIL: cqw scales with the card, not the viewport

I spotted 1.5cqw in a font shorthand inside opentunnel's stylesheet and had to look it up. Container query units are vw and vh for a component. 1cqw is 1% of the nearest container width, so a card can size itself to its own slot instead of the page.

csscontainer-queriescqwfrontend
postgres

I added a TIN index to my Postgres output and found the schema had never been written

PlanetScale shipped TIN, a BM25 text index for Postgres, and I wanted it as an option for my geocoding output. Adding one flag exposed that the function writing the table schema was fully unit-tested and had no production caller. Along the way I turned an outage banner into a design constraint and named a flag after a vendor.

postgresfull-text-searchbm25geocodingrustplanetscale
opencode

100% coverage, three live bugs: porting a plugin to OpenCode 2

I ported my OpenCode memory plugin to the OpenCode 2 API by the book: failing test first, 429 tests, 100% coverage. Then I pointed a real host at it and found the daemon had not run for 29 hours. Every bug that mattered lived in the contract with the host, where no unit test could reach.

opencodepluginsbunnodetestingsqlite
nuxt

One caret sign, 194 silent 500s: the outage that only broke on the second request

A reader told me my docs site had rendering problems and refreshing led to a 404. My own check returned a clean 200, because I asked once and they asked twice. The cause was two copies of Vue, and 194 server errors had been printing in every build log for two weeks.

nuxtvuepnpmssrcloudflare-workersdebuggingdependencies
cloudflare

TIL: Cloudflare was prepending Disallow rules above my robots.txt

I authored a robots.txt that explicitly invited AI crawlers. Production served a Cloudflare-managed block above it that disallowed eight of them. The repo never showed it, no API can turn it off, and RFC 9309 group-merging is the only thing standing between your allowlist and invisibility.

cloudflarerobots-txtai-crawlersseoaeorfc-9309
nuxt

Making a Nuxt site agent-ready: from Level 1 to Level 4 on isitagentready.com

AI agents are starting to operate websites, not just cite them. What it took to move a production Nuxt site on Cloudflare Workers from a score of 21 to Level 4 on the isitagentready scanner: markdown negotiation, API catalogs, agent-skills indexes, WebMCP, and the traps nobody documents.

nuxtcloudflareagentsmcpwebmcpseogeo
cloudflare

Workers Cache on an authenticated tile API: the split-entrypoint pattern

How to enable Cloudflare Workers Cache on an API that must authenticate and meter every request, using an uncached gateway entrypoint in front of a cached origin entrypoint. Warm viewport hit rate went from 10% to 100% and median warm tile latency dropped from 874ms to under 100ms.

cloudflareworkerscachepmtilesnitrogis
gis

Encoding elevation into pixels: DEM terrain tiles in Rust

How tileserver-rs turns raw GDAL elevation rasters into Terrarium and Mapbox-RGB terrain tiles that MapLibre renders as 3D terrain. The math, the classic 1-LSB encoding bug, and why the alpha channel will betray you.

rustgismaplibredemterraingdal
cloudflare

How I cut a $35 Supabase bill to $0 with Cloudflare D1

Moving a live multi-tenant SaaS off Supabase Postgres onto Cloudflare D1 with zero downtime. The schema rewrite was the easy part. The date-coercion bug, the PostGIS audit, and the break-even math were not.

cloudflared1supabasesqlitedrizzlenuxtmigration
nuxt

TIL: Shiki multi-theme highlighting does not switch the code-block background

Shiki multi-theme mode swaps token colours per colour scheme but leaves the pre background variable empty, so light-theme text can land on a dark block. You have to theme the container yourself.

nuxtshikinuxt-contentdark-mode
cloudflare

Generating dynamic Open Graph images on Cloudflare Workers

Rendering per-page and per-article social cards at the edge with Satori and @cf-wasm/og: how the route works, what Satori refuses to render, and the cold-versus-warm numbers.

cloudflareworkersopen-graphsatorinuxt
cloudflare

Migrating a Nuxt site from Cloudflare Pages to Workers

The full path from the cloudflare-pages preset to a cloudflare_module Worker: the config changes, the custom-domain cutover that no deploy step handles for you, and the gotchas that only show up in CI.

cloudflareworkersnuxtnitrodevops
css

TIL: Tailwind v4 mask utilities need the webkit prefix in plain CSS

Porting a Tailwind mask-composite utility to scoped CSS quietly breaks in Chrome and Safari unless you also write the -webkit-mask-* properties, because Tailwind was emitting them for you.

tailwindcsswebkit
cloudflare

TIL: Enabling Workers Cache through Nitro without a native option

Nitro forwards arbitrary keys into the generated wrangler config, so you can turn on Cloudflare Workers Cache with a single line even though there is no dedicated Nitro option for it.

cloudflareworkersnitronuxt
vue

Build & Publish Vue 3 Components using Rollup.js

Let's build a reusable Vue 3 component using rollup

vue 3rollup