Building Travel Vient: The Real Work Was Not Trusting Claude
How I built travelvient.com, a data-driven travel site with 80 airlines and a fleet of free tools, by letting Claude generate under skills I wrote and building the harness that catches it when it makes a fact up.
I travel a lot, and every single time I am standing in an airport doing the same frantic Googling. Does this bag fit Delta’s sizer. Do I have enough time to make a connection through Frankfurt with a checked bag. Do I need a power adapter for Portugal or just a converter. The answers exist, but they are buried in 2016 blog posts with popup ads, or spread across a forum thread, or flat out wrong.
I also wanted a real testbed. I wanted to see how far I could push Claude Code if I stopped treating it like an autocomplete and started treating it like a research team that needed management. A travel facts site is a perfect stress test for that, because travel facts are exactly the kind of thing a language model will cheerfully invent.
So Travel Vient is two things at once: a site I actually use when I fly, and an experiment in how to run a large content operation where most of the typing is done by an AI that cannot be trusted on its own.
What the thing actually does
travelvient.com is a static site with a lot behind it. Structured baggage data for 80 airlines. More than 80 destination profiles with costs, visas, and climate. Over 250 head-to-head comparison pages (airlines, destinations, cruises, eSIMs, travel apps) and 110 guides. On top of the data sit six free tools you can embed in any site with an iframe: a carry-on size checker, a custom bag-fit checker, a checked-bag fee calculator, a layover calculator, a power adapter finder, and an airline comparison widget.
Everything is data-driven. The pages are not hand-written prose with numbers sprinkled in. They are rendered from JSON, which matters a lot for the part that turned out to be hard.
How I worked with Claude on this one
This was a good mixture, and the ratio is the whole point.
I was the architect. I made every structural decision: the data schemas, the information architecture, which tools to build, how the site should be organized. Claude was a genuinely useful brainstorming partner and a good idea challenger when I was deciding those things, but I made the calls.
Then I inverted the usual setup. Instead of asking Claude to do a task, I built the skills that do the task well, and let Claude run inside them. There are Claude Code skills for generating a destination guide, for publishing a data-driven comparison post, for refreshing stale facts, and for shipping. Each one encodes the rules I care about so Claude does not have to rediscover them every time. Claude was instrumental in getting research done fast, but it was always running on rails I laid down.
And separately, I built tools to verify Claude’s work. Not to help it write. To check it after. That distinction ended up being the real project.
The stack
Astro 5 producing fully static output, deployed to Cloudflare Pages. No SSR adapter, because nothing needs to render on request. Tailwind v4 for styling, Pagefind for search, Satori for build-time Open Graph images, and a single small Cloudflare Workers function that writes anonymized tool-usage events to a D1 database.
The deliberate choice is that all the travel data is plain JSON in the repo, compiled into pages at build time. No database on the hot path, no API calls when someone loads a page. A carry-on checker is, fundamentally, a comparison between two objects in an array. It does not need a backend, so it does not have one. Claude suggested heavier approaches a few times, and I pushed back every time: the cheapest thing that serves correct data wins.
The tools also ship as embeddable iframes with a postMessage handshake for theme sync, so a travel blog can drop one in and it matches their light or dark mode. That was Claude’s clean implementation off a spec I wrote, and it has held up across a lot of host sites.
The hard part: factual integrity at scale
The hard part was never getting Claude to write pages. It was trusting the pages once they existed.
When you have 80 airlines and 250-plus comparison pages, every checked-bag fee is a liability. Airlines change fees constantly. Delta moved its first checked bag from one price to another partway through the project. A number that was right in March is a wrong-information lawsuit risk by June. Multiply that by thousands of fields and you cannot eyeball it.
My answer was to make provenance a required part of the data, not an afterthought. Every fact-bearing field carries a source URL and a last-verified date. Here is the actual shape of one airline’s carry-on block, and the rule was simple: Claude does not get to fill this in without filling in where it came from.
"carryOn": {
"allowed": true,
"dimensionsIn": { "length": 22, "width": 14, "height": 9 },
"linearIn": 45,
"weightLimitKg": null,
"feeUsd": 0,
"notes": "No weight limit on most routes. One carry-on plus one personal item on all fares.",
"sourceUrl": "https://www.delta.com/us/en/baggage/overview",
"lastVerified": "2026-05-29"
}
Then I wrote a lint that polices it. It does not touch the network. It just reads every data file and sidecar, assigns each collection a volatility tier, and flags anything past its freshness window or missing a source entirely. Airline fees go stale fast, so they get a 30-day tier; packing lists barely change, so they get 90. This is the core of it:
const VOLATILITY = {
'airline-comparisons': { tier: 1, days: 30 },
guides: { tier: 1, days: 30 },
'cruise-comparisons': { tier: 2, days: 60 },
'destination-comparisons': { tier: 2, days: 60 },
};
function daysSince(dateStr) {
if (!dateStr) return Infinity;
return (now - new Date(dateStr).getTime()) / (1000 * 60 * 60 * 24);
}
return Infinity for a missing date is the important line. No date means infinitely stale, which means it always gets flagged. There is no way for an unsourced fact to look fresh.
Where Claude surprised me
The thing that genuinely impressed me was how much accurate structured data Claude could produce in a single pass.
I expected to spend weeks hand-assembling the airline dataset, or to find Claude reaching for some half-broken library to scrape it. Instead, given the schema and pointed at official sources, it compiled the bulk of 80 airlines (dimensions in both inches and centimeters, weight limits, fees, basic-economy restrictions, gate-check risk) in a way that mostly survived my spot checks. The power-adapter tool needed data for over 200 countries and all 15 plug types, and it handled the genuinely annoying edge cases, like Brazil’s voltage varying by region, without me prompting for them.
It was also good at logic when the logic was well specified. The layover calculator’s buffer math is a real algorithm, and Claude wrote this kernel from my description of how connections actually work. It is not glamorous, but it is correct, and correctness is the entire job here:
let customsBuffer = 0;
if (intlArr) {
customsBuffer = ctx.customs ? (isPeak ? ctx.customs.peak : ctx.customs.offpeak) : 30;
}
let securityBuffer = 0;
if (!transferAirside && !sameTerm) {
securityBuffer = isPeak ? tsa.peak : tsa.offpeak;
}
const bagRecheckBuffer = hasBag && (intlArr || !bagInterlined) ? 20 : 0;
const recommendedBufferMin =
Math.max(mctMin, terminalTime + 30) + customsBuffer + securityBuffer + bagRecheckBuffer;
The speed of getting from “I want a layover tool” to “here is a working buffer model that accounts for customs, security, and bag recheck” was the real win. That would have been an evening of careful work by hand. It was twenty minutes of me reviewing.
Where Claude fell short
Claude’s worst habit, the one that shaped the whole architecture, is that it will hallucinate a fact rather than admit it could not find one.
Ask it for an airline’s overweight bag fee and, if the official page is hard to parse, it will sometimes return a confident, plausible, wrong number instead of saying it does not know. Plausible and wrong is the most dangerous output a research tool can produce, because it sails right through a casual review.
I could not prompt my way out of this reliably, so I built it out of the system. The refresh-facts skill has an explicit, non-negotiable rule about when a value may change and when it must be flagged for me instead. The relevant part reads, in effect:
A value change always requires 2 agreeing sources.
Single-source confirmation only covers the "no change" case.
NEVER auto-fix (always flag for a human):
- any case where sources disagree
- an ambiguous official read (online vs counter, member vs non-member)
- prose, verdicts, and FAQ answers
When a number genuinely varies by region or fare, do NOT guess a
representative value. Set the field to null and explain it in notes.
That last rule is the one that fixed the hallucination problem. The instinct Claude has is to put something in every slot. So I gave it a legal way out: when you cannot pin a single true value, write null and explain why. Flagging is success, not failure. Once “I could not source this” became an acceptable, expected answer, the made-up numbers mostly stopped, and the few that slipped through got caught by a human looking at a short flag list instead of scanning thousands of fields.
The shift was from trusting the output to trusting the process that produces and audits the output. I do not believe a number on Travel Vient because Claude wrote it. I believe it because it has a source, a date, and it survived a tool whose only job is to doubt it.
What went wrong overall
The biggest non-Claude mistake was an architecture decision I made early and paid for later: I put the software build logs and project pages on the travel domain.
It made sense at the time. One site, one place for everything. But the dev content quietly poisoned the site’s topic. Google and answer engines try to understand what a domain is about, and a travel-data site that also has React Native devlogs is muddier than one that does not. When I finally pulled the search numbers, the verdict was brutal and clear: the blog and projects sections pulled 1 click and 32 impressions out of 291 clicks and 104,732 impressions over 28 days. About a third of a percent. They were costing focus and returning almost nothing.
So I split them out to vient.org, the studio site this post lives on, with per-page 301 redirects so no link equity leaked. It was clean in the end, but it was a weekend of careful migration work that I created for myself by not separating the two from the start.
Where it is now
Travel Vient is live and is the busiest thing I run, at 314 commits and counting. After the split it is purely a travel site: data, guides, and tools, with the build logs over here on vient.org. The fact pipeline runs on a cadence, the embeddable tools are out in the wild on other people’s sites, and the analytics worker quietly tells me which tools people actually use.
It is not finished, because a facts site is never finished. Airlines will change their fees next week and the freshness windows will start flashing. That is fine. The point was to build something where that is a managed, visible process instead of a slow drift into being wrong.
What I would do differently
Keep the dev content off the travel domain from day one. This is the obvious one in hindsight. Two audiences, two topics, two domains. The split was avoidable, and avoiding it would have saved both the migration and months of a confused topic signal.
Build the verification before the generation, not alongside it. For a while I was generating content and building the fact-checking tooling in parallel, which meant some pages went live before the lint that would have caught their problems existed. The verification harness is the most valuable thing in the repo. If I did it again, the lint and the source-required schema would exist before the first guide was written.
And lean into skills sooner. The moment that changed this project was when I stopped asking Claude to do tasks and started building the skills that do tasks correctly, then letting it run inside them. That is what made an AI reliable at the scale of a thousand pages. The leverage was never in the prompts. It was in the rails.
Frequently Asked Questions
- Travel Vient (travelvient.com) is an independent travel research and tools site: structured baggage data for 80 airlines, destination guides, packing lists, power-adapter info, and free embeddable tools like a carry-on size checker, a checked-bag fee calculator, and a layover calculator. It is the parent product site of the Vient software studio.
- I acted as the architect. I made the structural decisions, designed the data schemas, and wrote a set of Claude Code skills (for generating guides, comparisons, and data posts, and for refreshing facts). Claude ran inside those skills to do research and generation quickly, while I reviewed the output and built separate tools to verify it. Claude was a strong brainstorming partner and idea challenger, but the big calls were mine.
- Every numeric or policy field in the data files carries a source URL and a last-verified date. A tiered research ladder requires the official source first, a real browser fetch second, and aggregator consensus only as a last resort. A refresh skill re-checks the stalest facts and will only auto-fix a value when two sources agree; anything ambiguous or contradictory is flagged for a human instead of being changed. A separate lint script reports anything missing a source or past its freshness window.
- Astro 5 producing fully static output, Tailwind CSS v4, Pagefind for search, Satori for build-time Open Graph images, and Cloudflare Pages for hosting with a small Workers function writing usage events to a D1 database. The travel data lives in plain JSON files that get compiled into pages at build time, so there is no database read on the hot path.
- The build logs and software projects originally lived on the travel domain, but they pulled about a third of a percent of the site's search clicks and muddied its topic for both Google and answer engines. I split them out to vient.org with per-page 301 redirects so Travel Vient could be purely about travel. The lesson was to keep the two audiences on separate domains from the start.
What is Travel Vient?
How was Travel Vient built with Claude?
How do you keep the travel facts accurate?
What tech stack does Travel Vient run on?
Why did the dev blog move to vient.org?
Founder of Vient and senior staff engineer
Caden Sorenson runs Vient, an independent studio building iOS apps, web tools, and client websites, including Travel Vient, a travel research site with everything cited to primary sources. He's a senior staff engineer with 15+ years of experience building iOS apps, web platforms, and developer tools, and a Computer Science graduate from Utah State University. Based in Logan, Utah.
Related posts
- I Built a Free Embeddable Carry-On Size Widget for Travel BlogsA free, no-cookies carry-on size checker any travel blog can embed with a short HTML snippet. 75+ airlines, auto-updating data, full theme customization.
- Building SeatCheck: A Car Seat Law Site Where a Wrong Number Could Hurt a KidOne of my kids wanted a booster seat, I didn't know if the law allowed it, and the answers online were terrible. So Claude and I built a cited car seat law site for all 56 US jurisdictions.
- Building a Travel Power Adapter Tool with Claude in a WeekendHow I turned leftover destination data into a 221-country power adapter finder with plug types and voltage comparison. The first version was unusable.