Anja Gutierrez
Production · two live sites

Abenteuer LIVE + GoIconicWay

Dual-brand bilingual sites for an EV campervan rental startup — and a guardrail that stops the LLM inventing addresses.

React 19Vite 6 SSGTypeScriptCloudflare PagesCloudflare WorkersGroqGitHub Actions
2Sites, one monorepo
48Pages
EN/DERuntime switching
6Guardrail patterns

What it is

Two brands for the same EV campervan rental business — a German-language travel journal and an English-language US site — built and deployed from one monorepo to two separate Cloudflare Pages projects via GitHub Actions.

Static site generation with Vite 6, runtime language switching rather than duplicated route trees, hreflang pairing across the 40 informational pages, lead capture through Google Apps Script, and Core Web Vitals tuned for the slowest realistic connection.

Both sites carry Klausy, an AI concierge running on a Cloudflare Worker.

The problem worth writing about

Klausy's job is to answer questions about routes and vehicles. What it kept doing instead was inventing a pickup address — a specific street, a suite number, a named airport terminal — because that is exactly the shape of text that follows “where do I pick up the van?” in a language model's training data.

This is a legal problem, not a cosmetic one. Under the rental terms, pickup is near the airport and never at the terminal, and the exact meeting point is agreed after booking. A fabricated address is a promise the business cannot keep.

The first fix was a stronger system prompt. That reduced the rate and did not reach zero, which is the whole lesson: a prompt is advisory, and advisory is not a guarantee. The guarantee had to live in code, on the response path, where it cannot be argued with.

The code

Pulled straight from the production codebase — unedited except for trimming.

worker/worker.js javascript
A deterministic guardrail on model output. Six patterns for the things that must never appear — street addresses, suite numbers, state+ZIP, a named airport, bare airport codes. Note the deliberate asymmetry in the airport rules: capitalized “McCarran International Airport” is caught, while lowercase “near the airport” is correct copy and passes untouched. On any hit the reply is discarded wholesale and replaced with approved copy — no attempt to patch the sentence, because a partially-corrected hallucination is still a hallucination. The console.error is intentional: it makes the trip rate visible in Workers observability instead of silent. Exported by name purely so it can be unit-tested without a live model call.
const PICKUP_RED_FLAGS = [
  // Street address: "4255 Spring Mountain Rd", "1 Main Street"
  /\b\d{1,5}\s+(?:[A-Z][\w.'-]*\s+){1,4}(?:Rd|Road|St|Street|Ave|Avenue|Blvd|Boulevard|Dr|Drive|Ln|Lane|Way|Pkwy|Parkway)\b/,
  // Suite / unit numbers
  /\b(?:Suite|Ste\.?|Unit)\s*#?\s*\d+/i,
  // US state + ZIP: "Las Vegas, NV 89102"
  /\b(?:NV|CA|AZ|UT)\s*\d{5}\b/,
  // A NAMED airport ("McCarran International Airport", "Las Vegas Airport").
  // Lowercase "near the airport" is correct and deliberately NOT matched.
  /\b(?:[A-Z][\w.'-]+\s+){1,3}(?:International\s+)?Airport\b/,
  // Airport codes, incl. the renamed-since-2021 McCarran
  /\b(?:LAS|LAX|SAN)\b/,
  /\bMcCarran\b/i,
];

// PICKUP_SAFE_REPLY holds the approved DE/EN fallback copy (omitted here).

export function sanitizePickupClaims(reply, lang) {
  const hit = PICKUP_RED_FLAGS.find((re) => re.test(reply));
  if (!hit) return reply;

  // Logged so Workers observability shows how often the model needs catching.
  console.error('Pickup guard tripped:', hit.toString(), '| reply:', reply.slice(0, 200));
  return PICKUP_SAFE_REPLY[lang] || PICKUP_SAFE_REPLY.de;
}

Screens

From the running application.

GoIconicWay hero with the Klausy AI concierge
GoIconicWay — English hero with Klausy open.

Want to see more of this one?

The repository is private, but I'm glad to walk through the codebase live — architecture, tradeoffs, the parts that went wrong first.