Skip to content

Website: ads, SEO and analytics

Research from 2026-10-23. A technical plan for the real website, not a final decision (see the open question on the website stack).

This page explains what the website needs technically so that ads work well, the salon is found on Google, and we can measure where bookings come from. How to run the ads themselves: Ads.

Summary

  • A plain HTML/CSS/JS site is enough. Everything below runs in the visitor's browser: no server, no database, no framework.
  • One URL per language (English at /, then /de/, /fr/, /it/), with the text in the HTML.
  • Shared CSS and JS; business facts from business.yml; a check script against drift between languages.
  • Cookie banner with consent first, then the Google tag and the Meta Pixel.
  • The booking is reported from the Cal.com embed to Google and Meta: this one event drives conversion tracking, ad optimisation and remarketing.

Structure (plain HTML, no generator)

We keep a plain static site, edited with an AI coding agent. The only real risk is drift: the same content exists in several languages, and one copy changes while the others do not. These rules keep it manageable:

Rule Why
One page per language, like the prototype: /index.html (English), /de/index.html, … plus Impressum and privacy policy Only about 4–8 HTML files
Shared files: one styles.css, one main.js (map, booking, tracking, consent) Design and logic exist only once; only the text differs per language
Text in the HTML, not switched by JavaScript Google indexes each language; ads land on the right language
Business facts (prices, hours, address, phone) taken from business.yml One place to change; the agent updates all language files from it
Check script (e.g. make site-check): same prices and phone in every language as in business.yml, hreflang on every page, same sections in the same order Catches drift automatically
Every change in all languages at once The same rule as for these docs (EN + RU)

Reconsider the approach (e.g. a static site generator) only if the site grows to many pages (blog, one page per service) or someone without AI tools must edit it regularly.

Small additions without our own server

Need Solution
Contact WhatsApp, phone and email links; or a form service [compare]
Selling vouchers online A payment link (e.g. Stripe Payment Links) or the booking tool's voucher feature
Server-side tracking (optional, later) Cal.com webhook → a small serverless function (e.g. Cloudflare Workers, Netlify Functions)

Hosting

Option Cost Note
Infomaniak (together with domain and email) A small hosting plan, a few CHF per month [verify] Data in Switzerland: simplest for the privacy policy
Cloudflare Pages / Netlify Free Fast, serverless functions included; US/EU companies, name them in the privacy policy
GitHub Pages Free Fine for the prototype; for the real site the options above are better

Languages and URLs

  • Separate URL per language: alpformflow.ch/ (English, the default), /de/, /fr/, /it/.
    • Ads: a German ad must land on a German page. On Google Search, the landing page language is now a matching signal (see Ads).
    • SEO: Google indexes each language separately only with its own URL.
    • Sharing: a link sent on WhatsApp opens in the right language.
    • Analytics: visits and bookings per language are visible directly.
  • <html lang="de"> (or en …) on every page.
  • hreflang tags on every page, pointing to all language versions, plus x-default.
  • / is the English page and also the x-default (for all other languages).
    • If the browser language is DE, FR or IT, a small dialog asks in that language whether to switch ("Möchten Sie diese Seite auf Deutsch sehen?"). Other browser languages see no dialog.
    • The answer is remembered, so the dialog does not come back. No automatic redirect: Google always sees the English page.
    • The cookie banner appears after the dialog is closed.
  • Language selector (a small drop-down in the header) links to the same page in the other language, not to the home page.
  • The current section is kept: while scrolling, the address follows the section (#prices, #faq …), so switching language opens the same section.
  • The prototype already uses this structure (since 2026-10-24).

Booking calendar language (Cal.com)

  • By default Cal.com shows the booking page in the visitor's browser language. We cannot choose it from the website.
  • Possible workaround (not decided yet, to discuss with the partners): one event type per language, each with its "Interface Language" set (event type settings; the API field is interfaceLanguage) [verify in our Cal.com account]:
Page Cal.com event type (example) Interface Language
/ anti-cellulite English
/de/ anti-cellulite-de Deutsch
/fr/ anti-cellulite-fr Français
/it/ anti-cellulite-it Italiano
  • Create the English one first, then Duplicate it for the other languages and change the slug, title, description and Interface Language.
  • All belong to the same person, so they share one calendar: a booking in one blocks the time in all of them [verify] with a test booking.
  • Our own texts (service name, description, booking questions) are written in the event type's language.
  • Keep them in sync: duration, price, availability and booking questions must be the same in all four. Change one → change all.
  • The website would then give each page its own event type in data-booking-cal (from business.yml). Today all pages use one event type.
  • Cal.com is loaded only on the first click on "Book", so visitors who don't book make no request to cal.com.

SEO basics

On every page

  • Unique <title> and meta description per language, with place and service: "Anti-Cellulite-Massage in Saanen | Alpform & Flow".
  • One <h1>, clear headings, the address and phone as text (not only in an image or the map).
  • Canonical URL (each language version points to itself).
  • Open Graph tags (title, description, image): the preview when the link is shared on WhatsApp, Instagram, Facebook.
  • Alt texts on images, in the page's language.

Structured data (schema.org)

  • JSON-LD block HealthAndBeautyBusiness or DaySpa, generated from business.yml: name, address, geo coordinates, phone, opening hours, prices, URL, booking link, images, sameAs (Instagram, Facebook, Google Business Profile).
  • Check with Google's Rich Results Test.

Files and tools

  • sitemap.xml with all language versions (and hreflang), robots.txt.
  • Google Search Console and Bing Webmaster Tools: submit the sitemap, watch indexing and search terms. Bing also feeds ChatGPT/Copilot answers. Both are set up for the "opening soon" page (step by step).
  • IndexNow (with the full site): tells Bing and the other engines that use it (Yandex, Seznam, Naver, Yep) right away which pages are new or changed, so updated prices or opening hours reach Bing, ChatGPT and Copilot faster. Google does not use it; for Google the sitemap stays the way.
    • A key file at the site root (e.g. https://alpformflow.ch/<key>.txt), generated by the site build.
    • After each upload, one request to https://api.indexnow.org/indexnow with the changed addresses and the key, as a step in the deploy command.
    • Not for the "opening soon" page: 4 pages that do not change, and Bing already reads the sitemap.
  • The same name, address and phone as on Google Business Profile and all listings (see Local search presence).

Speed (also an ad factor: slow landing pages cost more per click)

  • Images in WebP/AVIF, the right size, loading="lazy" below the first screen.
  • Fonts hosted on our own server, not loaded from Google Fonts (faster, and no data goes to Google without consent).
  • Little JavaScript; load the booking widget, map and tags only when needed.
  • Check with PageSpeed Insights (Core Web Vitals) on a phone.

Analytics and tracking

What we use

Tool Purpose Cookies / consent
Google tag (Google Ads conversion tracking) Cost per booking from Google ads, remarketing Yes, after consent
Meta Pixel Cost per booking from Meta ads, remarketing Yes, after consent
Website statistics: GA4, or a cookie-free tool (e.g. Plausible, Matomo, Cloudflare Web Analytics) [compare] Visits, pages, languages, channels GA4: after consent. Cookie-free tools: often without a banner [verify]
Google Search Console, Bing Webmaster Tools Search terms, indexing No (nothing on the website)

GA4 is free but needs consent, so it misses visitors who decline. A cookie-free tool counts all visits but does not feed Google Ads. Possible combination: a cookie-free tool for statistics plus the Google tag and Meta Pixel only for ads.

Events to track

Event When Why
page_view Each page Visits per language and channel
book_click Click on a "Book" button Interest; compare with bookings
booking_completed Cal.com reports a successful booking The main event: cost per booking, ad optimisation, remarketing
whatsapp_click, phone_click, email_click Click on a contact link Bookings outside Cal.com
directions_click (with app: google / apple / waze / android-chooser), copy_location Directions and copy location buttons Local interest
voucher_click Click on the voucher link Voucher interest

Reporting the booking from Cal.com

The Cal.com window is an iframe from another domain, so our tag cannot see inside it. The embed sends JavaScript events to our page; we pass them on:

Cal.ns["anti-cellulite"]("on", {
  action: "bookingSuccessfulV2",   // [verify] event name in the Cal.com embed docs
  callback: (e) => {
    gtag("event", "booking_completed", { method: "cal.com" }); // Google
    fbq("track", "Schedule");                                  // Meta standard event
  }
});
  • People who book via the direct link (Instagram bio, Google Business Profile) never visit our website. Cal.com has its own integrations (Google Analytics, Google Tag Manager, Meta Pixel) for its booking pages [verify] which plans include them.

UTM tags

Every link we place (ads, Instagram bio, Google Business Profile, QR codes, partners) gets UTM tags. The allowed values and the register of all links are on their own page: UTM links.

Example: https://alpformflow.ch/?utm_source=flyer&utm_medium=print&utm_campaign=opening-2026&utm_content=a6-station

Reading them needs no code: GA4, Plausible, Matomo and the ad platforms read UTM tags from the address of the first page automatically and remember the source for the whole visit. The website's job is to not lose them and to pass them on to the booking.

1. Do not lose the parameters

  • Redirects keep the query string: http → https, www → without www.
  • No UTM tags on internal links (menu, language switcher, buttons): they would start a new visit with a wrong source.
  • Canonical without parameters: <link rel="canonical" href="https://alpformflow.ch/de/">, so Google does not index ?utm_… copies of the page.

2. Pass the source on to the booking

The booking happens in the Cal.com iframe, so we connect booking_completed to the source ourselves:

// main.js: remember the campaign info of this visit (first page)
const p = new URLSearchParams(location.search);
const keys = ["utm_source","utm_medium","utm_campaign","utm_content","utm_term","gclid","fbclid"];
const utm = Object.fromEntries(keys.filter(k => p.get(k)).map(k => [k, p.get(k)]));
try { if (Object.keys(utm).length) sessionStorage.setItem("utm", JSON.stringify(utm)); } catch (_) {}
const saved = (() => { try { return JSON.parse(sessionStorage.getItem("utm") || "{}"); } catch (_) { return {}; } })();

// a) send the source with the booking event
Cal.ns["anti-cellulite"]("on", {
  action: "bookingSuccessfulV2",          // [verify] event name
  callback: () => gtag("event", "booking_completed", { ...saved })
});

// b) store the source with the booking in Cal.com (booking data, webhooks)
//    Cal.com accepts metadata as query parameters: metadata[key]=value  [verify]
const meta = Object.entries(saved).map(([k, v]) => `metadata[${k}]=${encodeURIComponent(v)}`).join("&");
document.querySelectorAll(".js-book").forEach(b =>
  b.dataset.calLink = `salon/anti-cellulite${meta ? "?" + meta : ""}`);
  • a) gives bookings per source in GA4/Plausible and Google Ads.
  • b) keeps the source on the booking itself in Cal.com, even when the visitor declined cookies [verify]. Useful later, e.g. "which channel brings clients who buy packages".
  • sessionStorage holds only campaign labels, no personal data, and is cleared when the tab closes. Mention it in the privacy policy [verify].

3. Analyse

Where What you see
GA4 → Reports → Acquisition → Traffic acquisition Visits by source/medium/campaign; with the key event booking_completed: bookings per source
GA4 → Explore Own tables, e.g. campaign × language page × bookings
GA4 → Admin → Channel groups A custom channel "Print" for utm_medium=print (GA4 does not know it)
Plausible / Matomo Sources with UTM filters; goals for bookings
Google Ads / Meta Ads Manager Cost and bookings per campaign and ad
Cal.com bookings / webhook data The source stored with each booking (point 2b), exportable to a spreadsheet

Every month: per channel, cost ÷ bookings = cost per booking, compared with the limit in the private notes.

Remarketing: how it works

Visitor opens alpformflow.ch/de/
  → Google tag / Meta Pixel load (only after cookie consent)
  → Google/Meta recognise the person (cookie + logged-in Google/Instagram account)
  → they add the person to OUR list "website visitors"

Visitor completes a booking in the Cal.com window
  → our page catches the Cal.com booking event
  → we send "booking_completed" to Google/Meta
  → they add the person to the list "booked"

In Google Ads / Meta Ads Manager:
  audience = "website visitors (last 30 days)" MINUS "booked"
  • We never see who is on the lists, only their size.
Platform Minimum list size to run ads How long people stay on the list
Google (Display / Demand Gen / YouTube) about 100 active users in the last 30 days [verify] up to 540 days
Google Search about 1,000 [verify] —
Meta about 100 people [verify] up to 180 days
  • Losses: declined cookies, Safari/iPhone privacy features, ad blockers. Perhaps 40–60% of visitors end up on the lists [verify].
  • Install the tags from day one: lists fill only from the moment the tag is live.
  • Later (optional): Meta Conversions API and Google enhanced conversions: the booking is also reported from a server (Cal.com webhook → serverless function), so fewer bookings are lost. Needs a privacy policy update.
  • Advertising tags (Google tag, Meta Pixel) set advertising cookies. Google requires consent for personalised ads from users in the EEA, the UK and Switzerland [verify], and many of our clients are EU tourists.
  • Cookie banner with a real choice: "Accept" and "Decline" equally visible; categories (necessary / statistics / marketing). Open-source libraries, e.g. CookieConsent (orestbida) or Klaro [compare].
  • Google Consent Mode v2: everything starts as "denied"; tags work fully only after "Accept". The Meta Pixel loads only after consent.
  • Without consent the visitor is not tracked and not added to any list. That is normal; we lose part of the data.
  • The banner itself in all site languages.
  • The privacy policy names every tool (Google Ads, Meta Pixel, statistics tool, Cal.com, map tiles, hosting). See Website & marketing law.
  • Map: the prototype loads OpenStreetMap tiles directly. This sends the visitor's IP address to the tile server: it is mentioned in the privacy policy template; alternatively load the map only after a click [verify].

Launch checklist

  • / (English), /de/, /fr/, /it/ with lang, hreflang, x-default, canonical, Open Graph; language dialog on /
  • Title and meta description per page and language
  • schema.org JSON-LD from business.yml
  • sitemap.xml, robots.txt; Search Console and Bing Webmaster Tools set up; sitemap resubmitted after the launch
  • IndexNow key file and the IndexNow request in the deploy command; launch announced to IndexNow
  • Fonts self-hosted, images optimised, PageSpeed checked on a phone
  • Cookie banner + Consent Mode v2 in all languages
  • Google tag + conversion booking_completed; Meta Pixel + Schedule event; tested with real test bookings
  • Contact click events, UTM tags on all our own links and QR codes
  • Cal.com event types and language checked (one event type, or one per language if decided), test booking; booking calendar starts on the opening day
  • Privacy policy and Impressum checked (fiduciary or lawyer)
  • make site-check passes

Sources