Disclosure: this post is by the Wanloria team (free, no ads, no affiliate links). It was drafted with AI assistance and fact-checked against the live site, per dev.to's guidelines on AI-assisted posts.
TL;DR: Wanloria is a free, bilingual (ES/EN) travel web app: verified places in 71 cities, 1-3 day itineraries with times, "free things" lists, signposted hiking trails with GPX and a weekly "this weekend" page. We built it largely by pair-programming with Claude Code and Cursor. The interesting part isn't the AI, though. It's the rules we gave it so it couldn't make things up.
The problem: travel content is mostly unverifiable
Most "things to do in X" pages are rewritten from other "things to do in X" pages. We wanted every fact on a page (opening hours, price, website, whether a trail is circular) to trace back to a source, or not be shown at all.
Data sources
- OpenStreetMap for places and trail geometry (ODbL, credited on every trail page).
-
Wikidata / Wikipedia for identity and context. Place URLs literally carry the ID:
/lugar/wiki-Q1140249/templo-de-debodand/lugar/osm-r4544509/museo-cerralbo. - Official websites for opening hours and prices, stored together with the URL they came from.
- Open-Meteo elevation (90 m DEM) for elevation profiles.
Rule 1: the LLM writes prose, not facts
Descriptions are generated by an LLM only from the verified fields of that place, never copying sentences from the source. If a fact isn't verified, the template doesn't render it. Every description is reviewed before it's published.
Rule 2: computed numbers use published formulas
Hiking time follows DIN 33466, with no stops:
// DIN 33466: 4 km/h on the flat, 300 m/h up, 500 m/h down.
function dinHours(km, ascentM, descentM) {
const horizontal = km / 4;
const vertical = ascentM / 300 + descentM / 500;
return Math.max(horizontal, vertical) + Math.min(horizontal, vertical) / 2;
}
dinHours(12.6, 493, 395); // 4.37 h → shown as "4 h 20 min"
That's exactly what the live page for the PR-C 164 near Barcelona shows (12.6 km, +493 m, 4 h 20 min, MIDE 3/5). Difficulty uses the MIDE 1-5 effort scale. Trails are only published if they're signposted (GR/PR/SL or equivalent), within 18 km of the city, doable in a day, and if the OSM geometry matches the official distance. Loose stages of long-distance routes are dropped.
Itineraries split a city's top places by area, order each day from most important to nearest, and estimate walking as straight-line distance + 30 % for streets at 4.5 km/h, with a lunch break. Visit duration is estimated by place type (a big museum isn't a viewpoint) and adjusted when the place publishes its own figure.
Rule 3: pages for humans and for crawlers
The app itself is a SPA, but every public page (city, place, trail, 1/2/3-day plan, collections like /gratis and /museos, and /este-finde) is server-rendered HTML with JSON-LD (TouristAttraction, ItemList, FAQPage, BreadcrumbList), hreflang es/en and a sitemap of ~7,000 URLs. There's also an llms.txt and a llms-full.txt with the core facts per city, each linking to its page.
What still goes wrong (honestly)
Verification is only as good as the field it verifies. One real example: a museum whose general ticket costs €3 but is free on Sundays got tagged "free", because the source said "entrada gratuita…" for certain time slots. The fix we're moving to is a richer price model (free, free_at_times, paid) rather than a boolean. Elevation from a 90 m model can be ±10 % off a GPS track. Opening hours drift, so every place page keeps the source link and users can report errors to hola@wanloria.cloud.
Try it / steal the ideas
- Methodology: https://wanloria.cloud/metodologia?utm_source=devto&utm_medium=article&utm_campaign=aeo-devto (EN: /en/methodology)
- Example city: Things to do in Madrid
- Example trails: Hiking trails near Barcelona
Feedback very welcome, especially on the price model and the trail filters.
Top comments (0)