[CODE]

Nine B&Bs, Three Months: What It Actually Took to Go Digital

In three months I built five websites for nine B&Bs in Hengchun, plus automated weekly reports and a Google Ads health check system. Here's what the work actually looked like — and what AI could and couldn't do.

7 min read AI-generated
case-study automation astro ai ga4 taiwan

One property was spending tens of thousands of NT$ a month on Google Ads. The keyword list hadn’t been touched in months. No conversion tracking — just budget going out and a rough sense that rooms were filling, so something must be working.

That wasn’t a configuration failure. The tools existed. The decision process didn’t. And the same gap showed up across all nine.

The nine B&Bs in Hengchun weren’t losing to each other. They were losing to a measurement and optimization loop that OTAs run continuously — one that no individual small property had ever been able to afford to replicate. Three months was about whether AI tooling changed that calculus.

The OTA advantage isn’t brand trust — it’s the machine that runs while you’re making beds

OTA commissions are far higher than guests assume. Operators pay them anyway because Booking.com and Agoda don’t just have traveler trust — they have full-time teams optimizing conversion rates, algorithms trained on years of behavioral data, and ad spend that makes any individual property’s budget irrelevant by comparison.

A direct-booking site can work for the segment of travelers who will book direct if the property is findable and credible. But only with a feedback loop: search-ranked pages, tracked conversion events, weekly analysis feeding back into ad decisions. Without the loop, you’re not competing with OTAs. You’re spending into a void while they close the gap.

Five codebases, three automated systems, nine properties. Three months.

Five sites designed to need nothing from me after deployment

Nine properties across five repositories — operators running multiple units share a codebase. One site outputs static HTML; the other four run SSR via @astrojs/node — not for real-time data needs, but because the admin panel requires server-side OAuth and a GitHub commit pipeline that can’t execute in a static context.

Astro 5 + Tailwind v4. Most pages prerender at build time; only the /admin routes execute at request time. The Node.js adapter runs as Express middleware rather than standalone, so compression and aggressive cache headers for static assets layer in before the Astro handler runs. Core Web Vitals came out healthy without manual tuning.

Content updates route through an admin panel backed by GitHub’s REST API — owners update room descriptions and pricing through a form that commits directly to the repository, CI rebuilds, no developer involvement. The constraint was intentional: nothing that requires my attention after deployment, content control that lives entirely with the owner.

GA4 was installed. An analytics process wasn’t.

All nine had GA4 running. None had a regular cadence that produced decisions.

The path from “open GA4” to “do something about it” requires finding the right report, recalling last week’s baseline, and translating a percentage change into an action — before the other tasks of the day displace it. For someone whose core work is hospitality, that path has too much friction every week to walk reliably. It wasn’t unwillingness. It was structural friction in the wrong place.

Each Monday morning, a three-stage TypeScript pipeline runs via GitHub Actions: pull GA4 data from the Data API, extract insights through an LLM analysis pass, render a Markdown report. That report is emailed directly to each owner. The analysis step receives the raw numbers alongside a context file — not auto-generated: it contains what I learned talking to each owner — peak seasons, which conversion events actually matter to their business, regional holiday patterns, what metrics they’ve historically acted on.

The LLM outputs plain Chinese. “Organic search dropped 15% this week — consistent with Dragon Boat Festival patterns, paid traffic held steady, not a campaign problem.” The pipeline defaults to Gemini 2.5 Pro; Claude available as an upgrade tier when deeper interpretation is warranted. Monday morning, inbox. No login, no dashboard, no translation from percentage to meaning.

The cron schedule runs twice — a primary at 08:37 Taipei time and a backup at 09:07 — because GitHub Actions occasionally drops a scheduled tick entirely without any error notification. The dedup guard makes the backup a no-op if the primary already committed that week’s report.

Tens of thousands a month on intuition

Some operators were spending significant monthly budgets on Google Ads with no systematic evaluation of keyword performance. Decisions ran on gut feel or didn’t happen at all.

An ads health check pipeline pulls keyword and performance data from the Google Ads API, passes it through four stages — fetch, analyze, render, verify — with an anti-fabrication gate at the end. The verify stage checks that every number cited in the report traces back to an actual data point in the source JSON; if a claim doesn’t pass, the render stage re-runs with targeted corrections, up to five attempts before failing clean.

Separately, keyword decisions land in Google Sheets — one row per keyword, with status, the date it was added, the date it was removed, and the reasoning at the time. I could have used a database. Cleaner schema, queryable history, proper normalization. I didn’t, because a database puts that history somewhere only I can access. Sheets means the owner can pull up every decision made on their account — when, why, and with what reasoning — without involving me. Owners treated it as their record, not a report I generate for them. That difference turned out to matter more for adoption than I expected.

The 14-day protection window is hardcoded and non-configurable: keywords added within the last 14 days are excluded from removal suggestions. The rule is enforced twice — once in the LLM prompt, and again in code as a hard filter before any operations are queued. Belt and braces, because under data pressure an LLM will occasionally ignore a rule it was told to respect. Under two weeks of data generates output that looks like analysis and is noise. I didn’t want the window to be adjustable because adjustable means someone removes it when they’re impatient for results.

The reports that generated action and the ones that didn’t

API integrations and automation pipelines took roughly a week. Getting the reports to produce follow-up conversations took longer.

The signal I tracked was simple: did this report result in a call about what to do next? A report that’s read and filed without action is expensive infrastructure that changed nothing.

What moved the needle had nothing to do with accuracy. The format that worked for each owner mirrored how they thought about their business — not how an analytics dashboard organizes information. One owner needed week-over-week occupancy, not traffic. Another acted on anything that touched the Dragon Boat period specifically. The context files exist because identical output that generates a phone call from one owner generates silence from another. The calibration is still running.

What the price point change actually required

Three months ago, this service didn’t exist at a price point these operators could afford. Analytics, ad optimization, weekly reporting, performant multi-property infrastructure — each piece previously required dedicated headcount or enterprise subscriptions. The LLM analysis layer handles work that would have required a dedicated analyst. GitHub Actions handles scheduling and CI that would have required DevOps time. Astro handles the performance work that would have required a frontend specialist. Together they compress operating cost to where the service is viable for clients who couldn’t have paid for any piece of it separately.

What AI didn’t provide: the judgment call about Sheets over a database, the identification of which friction was structural versus behavioral, or the discovery that accurate reports and actionable reports are different products. Those required understanding the owners’ actual situations — not their data.

The automation took a week. The calibration is ongoing.