OTAs dominate for three compounding reasons: massive ad budgets driving search and social traffic, years of established brand trust, and professional digital teams operating full-time. A small operator’s website doesn’t need to beat an OTA at all three — it needs to be functional enough that a guest who found her through search can book direct without friction. The gap isn’t ranking. It’s operational continuity. Someone has to manage the campaigns, instrument the tracking, audit the keywords on a regular cadence. Nobody in this budget tier does any of it — not because the work is hard, but because the per-client overhead of doing it consistently was always above what the category could absorb.
That’s the gap AI tooling actually solves here. Encode the strategy once. Every subsequent client starts from that baseline.
Four weeks, three skills: astro-promo-admin, ga4-ads-tracking, keyword-optimize. Each is a Claude skill file — an execution prompt, a decision record, working code templates. Together they cover the full ops loop: owner-managed promotional content, conversion tracking across every contact point, keyword targeting calibrated against real search behavior. The first engagement paid to build them.
The assumptions hardcoded in the first version
astro-promo-admin drops a complete admin backend onto any Astro 5 site. The owner authenticates via Google OAuth — access is gated to an email allowlist stored in the deployment environment, with no self-service account creation — edits promotional content through a web UI, and the change commits back to the repo via a GitHub App, triggering a site rebuild. A seasonal rate update requires no developer involvement.
The main flow took a day. The skill that installs cleanly into any client project took considerably longer.
Two non-obvious problems only surfaced when I tried to reuse the code rather than adapt it for each project individually.
First: Astro’s i18n routing in manual mode requires a middleware file or the build fails outright. But middleware mode and standalone mode behave differently under custom-server deployments. Custom servers typically handle static asset compression and caching; switching to standalone replaces the entire server and silently regresses Lighthouse scores on public pages. The skill now detects the deployment configuration and selects the appropriate Astro adapter mode — middleware preserves the existing static path and routes only the admin console through SSR; standalone is for projects that want Astro to own the server entirely. The choice looks like one config flag. The consequence is whether your Lighthouse numbers hold.
Second: the deploy proxy terminates TLS and forwards HTTP to Node. Astro’s built-in origin check compares scheme and host — the proxy makes the scheme http, so it 403s every mutating POST. The skill disables the built-in check and substitutes a HOST-only same-origin check on every admin write instead, which works regardless of what the proxy does to the scheme.
Then came the first demo on a new site. The owner clicked publish on her first promotion. The script failed silently. Nothing committed. A new site has no data file to write into. Seeding blank state on first run is one line. That line only triggers at the exact moment a new client is watching you demonstrate the product — the one moment you cannot absorb a silent failure. It’s in the skill now, marked as a mandatory non-negotiable.
A skill that works only for the first client is a script with undocumented prerequisites.
Constraints encoded in the prompt, not the docs
ga4-ads-tracking installs GA4 and, optionally, Google Ads conversion tracking. Two rules are encoded as hard execution constraints rather than checklist items.
The skill refuses to reuse another site’s GA4 Measurement ID. If no property exists, the skill stops and guides the owner through creating one before continuing. Shared properties merge traffic from multiple sites into an unsplittable stream. Once merged, the data is gone — weekly report numbers lose meaning from day one. I’ve seen this done with good intentions; the historical data doesn’t come back.
The skill also audits every clickable contact point on the site — phone, LINE, booking link, WhatsApp — and wires an event handler for each. It then writes this requirement into the project’s engineering standards: an untracked CTA is a bug, not an omission. Without per-channel granularity, there’s no way to tell an owner which contact method is producing inquiries. That’s the one number she actually acts on. A GA4 dashboard with no conversion events is a chart of noise.
By design, GA4 events and Google Ads conversions route through separate send_to destinations — neither bleeds into the other. When a client doesn’t run paid search, the Ads layer removes cleanly without touching the GA4 setup.
Checklists get skipped under pressure. Hard constraints in the execution prompt don’t.
The pipeline that verifies its inputs before it reasons
keyword-optimize is a TypeScript pipeline, not a prompt. It calls the Google Ads API to pull search term data for a configurable lookback window (minimum 60 days, recommended 60–90), crawls the client’s live website — homepage and up to five linked pages — to extract context about property features and location, then calls an LLM with both inputs and requires structured output. The result is a YAML file for human review. A separate script dry-runs the proposed changes before any of them touch the Ads account.
Before any of that, Phase 0: confirm account access, confirm the data window has sufficient history, confirm the search terms report is populated. Without those checks, the LLM reasons over incomplete input and produces recommendations that look plausible but aren’t grounded in real traffic.
Six iron rules govern the output. Keywords with strong impressions but zero contact-point conversions over a sufficient window get paused, not further optimized. Competitor brand terms with conversion signal get kept — those are evaluation queries from guests actively comparing options. Keywords below a minimum impression threshold get held regardless of LLM confidence. A cleanup mode handles accounts inherited in disarray: broader history window, sharper action thresholds. Each rule carries its reasoning inline.
I added the reasoning after watching the LLM find creative justifications to bypass rules stated as bare instructions. The logic is what holds, not the instruction alone.
The output is a prioritized YAML with supporting data: the search terms that triggered ads, the ones wasting budget, the recommended changes. The owner can act on it without logging into the Ads dashboard.
What the second client inherits
The middleware mode detection, the blank-data first-launch seed, the per-property GA4 enforcement, the six iron rules with their reasoning — all paid for on the first engagement. The skill carries them into the next project. The next client starts from solved problems. The margin doesn’t need to absorb relearning them.
This is what the AI framing actually means here. It wasn’t that AI replaced a team or made development faster. The direct booking ops this stack enables — conversion tracking that distinguishes phone from LINE from the booking link, keyword targeting calibrated against actual search terms, owner-managed promotional content, keyword recommendations the owner can act on without opening a dashboard — was economically unviable for this category of client before. A boutique hotel can absorb the cost of a dedicated digital team. A small guesthouse in a tourist town cannot.
What changed is that once the strategy is encoded into skills, the overhead of running these ops correctly for the next client collapses to nearly zero. That’s the economic shift. Not cheaper per engagement — the first engagement is real investment. What scales is that the per-client cost of doing it right, after that, is the cost of applying a skill rather than rebuilding an approach.