top of page

Generating Interactive Travel Content at Scale Using AI - What Actually Worked

  • Writer: Mark Waldron
    Mark Waldron
  • Mar 17
  • 7 min read

The Problem

In my previous post I talked about automating translations with AI. That solved the "how do I keep multiple languages in sync" problem quite nicely. But it exposed a bigger one. I didn't have much content worth translating.


Something for the Bots

I was working towards the first MVP release of When To Travel. The whole point of getting something out the door was to start appearing in search results. If nobody can find the site, if nobody knows about it, then it doesn't matter how clever the architecture is. For that, I needed content. Actual pages that search engines could index. I believe you really need to consider the bots, the crawlers and all the other non-humans out there when developing a site like this. In this first stage of the site I must consider them as a primary audience, without loosing sight of the human audience of course.


Something for the Humans

To target specific travel destinations I didn't want to just throw darts at a map. I used Claude to research which combinations of locations and locales had the highest revenue potential. The insight I kept coming back to was that the non-English-speaking travel market is massively underserved. Most travel content is written in English first and maybe translated as an afterthought, if at all. That felt like an opportunity. Target locales where there's real search demand but far less competition.


I combined that locale revenue analysis with long-tail location targeting. Rather than going after "Paris" or "Tokyo" where I'd be buried under established sites, I looked for popular destinations that have unique name variations in the languages I was targeting. A destination that's searched differently in German or Japanese than it is in English gives you a natural long-tail keyword advantage. That combination of high-potential locale plus high-potential location became my seed set. Around 70 destinations across 5 languages. Enough to launch with, with room to expand later.


Introduce Play

There was one other thing nagging me though. I didn't want to ship a wall of AI-generated text and call it a day. The internet has enough of that already. My angle was to build something interactive. Tools and features that let the user actually engage with the data, compare months, filter by what matters to them. The generated content is the foundation, but it's the interactivity that makes it worth visiting. Content gets you found. The product keeps people around.


When To Travel has around 70 destination pages. Each page needs month-by-month travel ratings, festival listings, sections for different traveller preferences, an introduction, and an SEO description. In English. Then in German, French, Spanish and Japanese. That's roughly 350 content files. I am one person. Writing those by hand was never going to happen.

The translation script had already proven that AI could handle structured, repeatable tasks well. The question was whether it could handle something more creative — and whether I could trust the output enough to actually publish it.


The Naive First Attempt

My first go was simple. One big prompt, one API call. "Generate all the content for Bali." Out came a blob of data and prose. It was... fine. But the structured data and the prose didn't always agree. A month described as "avoid if you can" might have a rating of 4 out of 5. The model was trying to juggle too many things at once and it showed.


I deployed it to a staging version of the site anyway and got some actual people to look at it (mostly my wife). The feedback was blunt: "it sounds like AI." They weren't wrong. It did. The content was technically accurate but it had that unmistakable flavour. Every sentence polished to the same generic sheen, no rough edges, no personality. Readable but forgettable. The Ai wasn't really able to generate something very refined, it needed to concentrate on too much at once. I have noticed this wilst working with the LLM, if you tell them to do too many things at the same time they just do each seperate thing a little bit worse. I've seen this pattern before in software. When a function does too many things you get unpredictable results. Same principle applies here.


Two Passes

I split the work into two separate API calls.

IDE screenshot showing the content generation script output alongside the terminal running it

Pass 1 is the data expert. It generates structured information. Ratings for each month across different categories, notable festivals with dates, section headings for traveller types, and an SEO meta description. The output is validated against a strict schema using Zod. If the shape is wrong, it fails immediately. No guessing.


Pass 2 is the writer. It receives the Pass 1 data as context and writes prose to match. An introduction and body text for each section. Because it's working from the data, the prose can't contradict the ratings. A month rated 2 out of 5 gets described accordingly.

This separation was a turning point. Each pass could focus on what it was good at. The data was consistent. The prose was grounded. And I could rerun Pass 2 independently if the writing needed improving without losing the underlying data. That --pass2-only flag turned out to be one of the most useful things in the whole script.


The Prompt Is Where the Quality Lives

I learned fairly quickly that the quality of AI output is directly proportional to how specific your instructions are. Vague prompts produce vague content. Specific prompts produce usable content.


My prompts got very detailed. I defined the exact JSON schema I wanted back. I specified the rating scale (0-5). I gave the model a persona for each pass. "You are a travel data expert" for Pass 1, "You are a travel writer" for Pass 2. For each locale I injected specific rules about formality and register. German content must use the formal Sie, Japanese uses desu/masu polite style, French uses vous.


Then there was the banned words list. After generating the first batch of English content I read through it and winced. Everything was "vibrant". Every coastline was "breathtaking". Cities were "nestled" between mountains. There was a "tapestry" of culture around every corner. It read like AI wrote it, because AI did write it.

So I compiled a list of about 30 words and phrases. Vibrant, breathtaking, nestled, tapestry, hidden gem, must-visit, unforgettable experience, em dashes. I added them to the prompt as hard prohibitions. The improvement was immediate and significant. It's a blunt instrument, but it works. I found it quite entertaining to add to this list over time. You start to develop an eye for "AI speak" and every time you spot a new one it goes on the list.


I also had a problem with repetitive openings. Without intervention, every introduction started with some variation of "Welcome to [destination], a [adjective] [noun] that...". To fix this I created a pool of 20 different narrative approaches. Start with a sensory moment, lead with a contrarian take, open on a specific scene, use a seasonal contrast. The script picks one deterministically based on the location. Different location types get different styles. I can add to this over time, at the moment if you read lots of prose for lots of destinations you do start to see the repetition. I may need to work on this but it's good enough for now.


The Model Doesn't Always Listen

Even with detailed prompts, the output sometimes needs cleaning up. The model would occasionally return English month names in a Japanese content file. Once or twice it generated 13 months (I still have no idea why it thinks there are 13 months in a year). So the script has post-processing steps. Normalise month names by position, enforce exactly 12 entries, validate the schema. Trust the model, but verify the output.

This is an important mindset. AI output is a first draft, not a finished product. Post-processing is not a sign that the AI failed. It's just part of the workflow.


Running It on Azure OpenAI Too

I also built a second version of the script that runs on Azure OpenAI (GPT-4.1, GPT-4o) instead of the Anthropic API. Partly because I had Azure AI Foundry credits to use, partly because I wanted to understand the portability of the approach.

The core architecture transferred fine. Same two passes, same prompts, same output format. But the output quality was noticeably worse. The content from GPT-4.1 and GPT-4o read flatter and more generic than what Claude produced. I could see it myself, but I didn't want to just trust my gut on that, so I fed samples from both into Gemini as a neutral third party and asked it to evaluate. It agreed. The Claude output was consistently stronger.


So I shelved the Azure OpenAI approach for now. I still think there's value in keeping the script portable. Azure AI Foundry supports third-party models through a marketplace add-on, including Claude, and the automation tooling in Foundry looks genuinely useful. When I get to the point where I need that kind of orchestration I'll revisit it. For the MVP though, the Anthropic API direct was the clear winner.


What I Ended Up With

By the end of this phase I had generated content for all 69 locations in 5 languages. Around 350 Markdown files with YAML frontmatter for the structured data and prose sections below. They slot directly into the Next.js build pipeline.

Was the content perfect? No. Was it good enough to publish with some review? Yes. And that was the goal. Get to 80% with automation, spend human effort on the remaining 20%.


A full run for all locations in a single language costs less than $5 in API calls. Doing the same with freelance writers or a content agency would cost orders of magnitude more. Weeks instead of minutes. The economics are hard to ignore. And I have some ideas that should bring that cost down significantly, but that's an optimisation for a later stage. For the MVP, sub $5 per language is more than acceptable.

But the content needed checking. And that's the next post.

Comments


bottom of page