Skip to content

fix(seo): global geographic resilience with admin_level_1 and Nominatim#177

Merged
vgpastor merged 4 commits intomainfrom
fix/geography-seo-resilience
Apr 13, 2026
Merged

fix(seo): global geographic resilience with admin_level_1 and Nominatim#177
vgpastor merged 4 commits intomainfrom
fix/geography-seo-resilience

Conversation

@vgpastor
Copy link
Copy Markdown
Contributor

Summary

  • Adds admin_level_1 field to AedLocation — stores region/state derived from coordinates via Nominatim reverse geocoding (works worldwide, not just Spain)
  • Enriches admin_level_1 during CSV import and external sync using the public Nominatim API
  • Uses admin_level_1 as the primary source for geographic hierarchy in sitemap, country page, and region pages (with INE code fallback for non-enriched records)
  • Fixes 10+ SEO bugs: wrong city-to-region mapping, fuzzy city name match returning wrong cities, sitemap duplicates, 307→308 redirects, IndexNow key files, JSON-LD escaping

Bug Fixes

  • City→region mapping: cities no longer assigned to wrong communities (root cause: import data used district codes instead of INE codes)
  • resolveCityName: removed dangerous contains fallback that could match "Tor" → "Torrevieja"
  • Legacy redirect: now uses admin_level_1 fallback when city_code is null
  • Sitemap: deduplicated URLs, added 50K size bound, added error logging
  • Nominatim language variants: added 19 English aliases for Spanish communities
  • communityForIneCode: O(n*m) → O(1) via Map
  • Rate limiter: concurrency-safe via promise-chain serialization
  • Coordinate check: if (lat && lon)if (lat != null && lon != null) (0.0 was falsy)
  • safeJsonLd: now escapes & and > for XML parser compatibility
  • Redirects: 307 → 308 permanent for SEO link equity transfer

New Files

  • src/lib/nominatim.ts — Nominatim reverse geocoding utility (rate-limited, retry with backoff)
  • scripts/enrich-geography.ts — CLI script for bulk enrichment of existing records
  • prisma/migrations/20260414200000_add_admin_level_1/ — Adds field + backfills from INE codes
  • public/{key}.txt — IndexNow key verification files

Test plan

  • npm run type-check passes (only pre-existing S3 module error)
  • npm run lint passes (0 errors, only pre-existing warnings)
  • Pre-commit hooks pass (eslint + prettier)
  • Migration is non-destructive (ADD COLUMN + UPDATE only)
  • Verify sitemap generates correct URLs after migration
  • Run npx tsx scripts/enrich-geography.ts --limit 10 --report to test enrichment

- Add admin_level_1 field to AedLocation for coordinate-derived region names
- Add Nominatim reverse geocoding utility (src/lib/nominatim.ts) with
  concurrency-safe rate limiting for OSM public API
- Enrich admin_level_1 on import (CSV and external sync processors)
- Use admin_level_1 as primary region source in sitemap, country and
  region pages, with INE code fallback for non-enriched records
- Add community name aliases (English variants from Nominatim)
- Optimize communityForIneCode from O(n*m) to O(1) with Map
- Fix resolveCityName: remove dangerous `contains` fallback that could
  match wrong cities non-deterministically
- Fix legacy city redirect to use admin_level_1 when city_code is null
- Fix redirects from 307 to 308 (permanentRedirect) for SEO
- Deduplicate sitemap URLs and add 50K size bound
- Fix safeJsonLd to escape & and > for XML parser compatibility
- Fix falsy zero coordinate check (0.0 lat/lon treated as null)
- Add error logging in sitemap catch block
- Fix IndexNow key files to match protocol requirements
- Add scripts/enrich-geography.ts for local bulk enrichment via Nominatim
- Migration backfills admin_level_1 from existing INE province codes
@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Apr 13, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
dea-map Ready Ready Preview Apr 13, 2026 10:48pm

Request Review

- Filter sitemap city URLs by known communities to prevent 404s from
  foreign/unrecognized admin_level_1 values
- Skip Nominatim reverse geocoding in updateAed when coordinates have
  not changed (avoids redundant API calls on repeated syncs)
- Reduce Nominatim retries from 3 to 1 for server-side import calls
  to prevent blocking import processor ~20s/record when API is down
  (CLI script keeps retries=3 for thoroughness)
- Use PrismaPg adapter (consistent with other project scripts)
- Pass DATABASE_URL via environment variable
- Validate DATABASE_URL presence before starting
- Add nominatim_verified_at field to track which records have been
  verified via Nominatim (supports resume without reprocessing)
- Skip records where Nominatim country != stored country_code
  (coordinates don't match country — excluded from enrichment)
- Skip Null Island (0,0) coordinates, mark as verified
- Cache geocode results for identical coordinates (saves API calls)
- Generate wrong-country CSV report for manual review
- Default --only-unverified: only processes records not yet verified
- Use --all flag to reprocess everything
@vgpastor vgpastor merged commit f2854b3 into main Apr 13, 2026
13 checks passed
@vgpastor vgpastor deleted the fix/geography-seo-resilience branch April 13, 2026 22:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant