English version available Switch to English →
Studiu de caz · Google Ads landing pages

Cum am ridicat Quality Score-ul pentru un client UK cu buget mare.

10 minute citire · Publicat 14 mai 2026 · Mugur Marinescu

Client din servicii de urgență la domiciliu, acoperire în 94 de zone poștale UK, buget Google Ads cu șase cifre lunar. Reclamele erau bune. Site-ul trăgea Quality Score-ul în jos. Am construit 656 de landing pages noi pe Astro + Cloudflare. PageSpeed mobile 47 → 98. Estimare Quality Score: 6.25 → 8.5+ în 4-6 săptămâni post-lansare.

Discutăm un proiect similar → Înapoi la studii
1. Diagnostic

Reclamele bune nu salvează un site slab.

Există o categorie de clienți care nu vin la mine pentru SEO. Vin pentru că au campanii Google Ads care cheltuie mult în fiecare lună și simt că pierd bani. Reclamele scrise bine, keyword-urile gândite, extensiile setate. Și totuși costul per conversie crește lună de lună, iar Google le spune că „Expected CTR" și „Landing Page Experience" sunt sub medie.

Clientul ăsta era exact în această situație. Activează în servicii de urgență la domiciliu pe piața UK, are acoperire în peste 90 de zone poștale și rulează campanii separate per zonă. Buget cu șase cifre lunar. Conversiile veneau, dar costul per call ar fi putut fi mult mai mic dacă landing pages-urile ar fi funcționat cum trebuie.

L-am preluat după două agenții care i-au optimizat reclamele dar nu s-au atins de site.

Diagnostic Quality Score Google Ads — semnale despre ce trebuie reparat
Diagnostic inițial — Ad Relevance ok, Expected CTR și Landing Page Experience sub medie.
2. Audit

Ce găsește un audit serios de Quality Score.

Înainte să ating cod, am cerut acces la conturile Google Ads și am exportat trei rapoarte: Campaign Performance, Keyword Performance și Landing Pages Report — toate pe ultimele 30 de zile.

Trei semnale mi-au sărit imediat în ochi.

Ad Relevance — „Above Average"

Pentru majoritatea keyword-urilor, textele reclamelor erau bune. Problema nu era în copy.

Expected CTR — „Below Average"

Pentru două treimi din keyword-uri. Asta nu ține de copy — ține de cât de încrezător decide algoritmul Google că oamenii vor da click. Și asta se vede din historic: dacă landing page-ul nu convertește bine, Google reduce Expected CTR.

Landing Page Experience — „Below Average"

Pentru aproape un sfert dintre keyword-uri.

Cu alte cuvinte: reclamele erau în regulă. Site-ul trăgea totul în jos.

3. Site-ul vechi

Patru probleme structurale care omoară Quality Score.

Site-ul existent era pe WordPress cu o temă pentru servicii de urgență. La prima vedere arăta corect: responsive, numere de telefon vizibile, pagini per oraș. La a doua vedere am găsit patru probleme structurale.

1. Inconsistență de telefon între pagini

Pe homepage apărea un număr 0800 național. Pe pagina unui oraș apărea un număr local complet diferit, dintr-un cu totul alt prefix decât orașul respectiv (prefix Reading pentru o pagină dedicată unui oraș din Țara Galilor). Google penalizează inconsistența de NAP — Name, Address, Phone — atât în Quality Score, cât și în local SEO. Nu îți spune explicit „te penalizez pentru asta", dar trust signal-ul scade și se reflectă în CTR.

2. AggregateRating exista în schema markup, dar nu era vizibil în pagină

Datele structurate spuneau Google că businessul are 4.9 stele și mii de recenzii. Vizitatorul, ajuns pe pagină, nu vedea nicăieri acest semnal de încredere. Apărea discrepanță între ce promiteau reclamele Google (uneori extragea ratingul în SERP) și ce vedea utilizatorul la aterizare. Expected CTR se duce în jos.

3. Un singur landing page pentru ad group-uri cu intent foarte diferit

Keyword-ul „locksmith bristol" și keyword-ul „locksmith near me" duceau la aceeași pagină. Pentru Google asta înseamnă că pagina ta nu match-uiește exact intenția. Landing Page Experience scade.

4. H2-uri redundante

„Why Choose Us" apărea de trei ori pe aceeași pagină. Genul de chestii pe care Lighthouse nu le marchează dar care fac pagina să pară spam keyword stuffing.

Comparație site vechi WordPress vs site nou Astro pe mobile
Comparație înainte/după pe mobile — site vechi vs site nou.
4. Decizia

Site nou, nu refacere.

Două variante pe masă:

Varianta A: lucrăm pe site-ul WordPress existent. Reparăm telefoanele, adăugăm rating-ul vizibil, separăm pagini pe intent, curățăm H2-uri. Estimare: 60–80 ore de lucru, plus testare, plus risc de regresie pe componente pe care nu le-am scris eu.

Varianta B: construim site nou, complet separat de cel actual. Domeniu nou, 6 template-uri per zonă poștală, deploy pe Cloudflare Pages. Site-ul vechi rămâne pentru organic. Site-ul nou e dedicat exclusiv traficului plătit. Estimare: 80–100 ore.

Am ales B. Trei motive: zero risc pe traficul organic existent, posibilitate de A/B testare clean (poți deruta o parte din trafic Google Ads spre noul site și compari), și libertatea de a alege stack-ul potrivit pentru ce face site-ul ăsta — convertit trafic plătit în calls.

5. Stack-ul

Astro static, nu Next.js sau WordPress.

Pentru un site care primește exclusiv trafic Google Ads, prioritățile sunt diferite față de un site SEO clasic:

  • Trafic 95% first-touch. Cineva caută „locksmith bristol", dă click pe reclamă, ajunge pe site, ori sună ori pleacă. Caching CSS între vizite nu prea contează.
  • LCP sub 2 secunde e obligatoriu, nu nice-to-have. Google Ads măsoară Landing Page Experience parțial pe baza speed-ului real. Utilizatorul mobile pe 4G slab abandonează după ~3 secunde.
  • Sunt căutări de urgență. „I'm locked out" nu așteaptă 5 secunde să apară prețurile. Site-ul trebuie să-l ducă pe utilizator de la prima imagine pe ecran la butonul de call în sub o secundă.
  • Zero formulare. Lead-ul aici e apelul telefonic. Formularul adaugă fricțiune care nu se justifică în urgență.

Stack-ul ales:

  • Astro 6 cu output static. Generează HTML pur, fără React runtime, fără hydration. Bundle JavaScript pe pagină sub 5KB total — practic nimic. Componente componibile (.astro fișiere), routing file-based, build-ul scoate doar fișiere statice.
  • Tailwind CSS 4 prin plugin Vite. Stilurile critice inline-uite în HTML, nu se descarcă separat.
  • Cloudflare Pages. Hosting gratuit, CDN global cu sub 100ms TTFB în orice țară, integrare directă cu GitHub pentru deploy automat la push.
  • Cloudflare Web Analytics pentru tracking de bază fără cookies (privacy-friendly, GDPR-safe).
  • Google Tag Manager pentru tracking de Phone Call Conversions — singura conversie care contează aici.

Cu stack-ul ăsta am generat 656 de pagini statice (99 orașe principale × 6 template-uri, plus 45 orașe medii, plus 6 hub-uri de serviciu, plus paginile legale și homepage), toate cu LCP sub 2.5s la prima măsurare.

6. Intent matching

6 template-uri × 99 orașe = 594 pagini per intent.

Analiza Google Ads mi-a arătat exact ce ad group-uri rulează și care converteau cel mai bine. Am derivat 6 template-uri, fiecare match-uind un intent specific:

Template URL Ad group Search exemplu
/locksmith-near-me-{oraș}/Locksmith Near Me„locksmith near me"
/locksmith-{oraș}/Locksmith [city]„locksmith bristol"
/emergency-locksmith-{oraș}/Emergency Locksmith„emergency locksmith"
/locked-out-{oraș}/Lock Unlock„locked out"
/lock-change-{oraș}/Lock Change„replace locks"
/upvc-door-repair-{oraș}/uPVC Locksmith„upvc door repair"

În loc să trimit orice utilizator la o singură pagină „Bristol Locksmith", trimit fiecare ad group la pagina care match-uiește exact ce a tastat el. Hero-ul pe /locked-out-bristol/ începe cu „Locked Out in Bristol? We're 15 Minutes Away" — exact ce caută cineva care a tastat „locked out bristol" la 2 dimineața.

Quality Score-ul Google răsplătește potrivirea asta. Landing Page Experience urcă imediat când relevanța crește.

Structura URL — 6 template-uri per zonă potrivite cu ad groups
Diagrama structurii — 6 intent-uri × 99 orașe principale = 594 pagini.
7. Detaliile

Ce face sau strică o pagină Google Ads.

Telefonul: un singur număr național, hardcoded

Pentru a elimina inconsistența NAP, am ales o singură variantă: un 0800 național pe toate paginile. Header sticky, hero, mid-page CTA, FAQ, footer, sticky mobile bar, schema.org telephone field, toate link-urile tel:. Numărul e hardcoded într-un singur fișier de config TypeScript. Schimbi acolo, se schimbă peste tot.

E o decizie de business, nu doar tehnică. Numere locale dau iluzia că ești „local", dar pentru un serviciu cu acoperire națională, inconsistența e mai costisitoare decât avantajul. În plus, 0800 e gratuit pentru apelant — argument suplimentar.

Rating widget vizibil în hero

Schema markup-ul cu AggregateRating rămâne acolo unde era, dar acum apare și ca widget cu stele galbene în hero, deasupra fold-ului. Două badge-uri side-by-side: rating Google + rating de pe o platformă secundară. Asta rezolvă Expected CTR scăzut — utilizatorul vede semnalul de încredere imediat și mai degrabă rămâne pe pagină.

Sticky mobile call bar

Pe mobile, un buton fixat în partea de jos a ecranului care spune „Tap to call" și include numărul. Vizibil în 100% din timp pe orice scroll. Apare după ce hero-ul iese din viewport — implementat cu IntersectionObserver, nu cu scroll listener (zero forced reflow, zero JavaScript pe main thread la scroll).

Postcode districts reale, nu generice

Pe pagina dedicată orașului Bristol, secțiunea „Coverage in BS Area" listează toate cele aproximativ 50 de district codes reale (BS1, BS2, ..., BS49). Datele au fost extrase din Wikipedia pentru toate cele 99 de orașe principale — în total peste 2300 de coduri poștale. Nu copii. Nu generăm.

Pentru utilizator: încredere („cunosc zona mea exact"). Pentru Google: conținut unic și relevant per pagină — Landing Page Experience urcă.

FAQ scris specific pe oraș

Fiecare combinație oraș + template are 6 întrebări în FAQ, scrise specific pentru contextul orașului respectiv. Nu „lorem ipsum" generic. FAQ-ul de pe /locksmith-bath/ menționează clădirile georgiene din piatra de Bath și încuietorile multipoint moderne din uPVC. FAQ-ul de pe /locksmith-cambridge/ menționează casele student rentals și locking-ul HMO regulations.

Toate au schema FAQPage atașată. Apar uneori ca rich snippets în SERP — încă un boost pentru Expected CTR.

Hero cu rating widget vizibil + buton call mare
Hero cu rating widget vizibil deasupra fold-ului — trust signal explicit.
Hero pe mobile — primul ecran vizibil când aterizezi din Google Ads
Hero pe mobile — primul ecran după click pe Google Ads.
8. Performance

De la 86 la 98 în 3 runde.

Build-ul inițial pe Astro a ieșit din box la 86 Performance / 96 Accessibility / 100 Best Practices / 100 SEO pe mobile. Bun. Nu excelent. Am avut nevoie de trei runde de optimizare ca să ajung la 98 / 100 / 100 / 100.

Runda 1: fonts și GTM

Fonts. Foloseam Inter și Fraunces de pe Google Fonts CDN. Lighthouse marca un request chain fonts.googleapis.com → fonts.gstatic.com → fișierul propriu-zis cu latency total aproape 250ms. Mai rău, CSS-ul de la Google Fonts e render-blocking. Am descărcat ambele fonturi ca woff2 variable, doar subset-ul latin (suficient pentru UK English), le-am pus în /public/fonts/ și le-am declarat cu @font-face local. Plus <link rel="preload"> în head pentru cele două fișiere. Total fonts: ~85KB woff2 self-hosted, gata în primele 100ms ale conexiunii.

Google Tag Manager. GTM era încărcat sync la fiecare pagină — 73KB JavaScript blocking main thread înainte ca utilizatorul să vadă conținutul. Lighthouse marca TBT (Total Blocking Time) la 110ms. Am amânat GTM până la prima interacțiune sau 3 secunde idle, oricare vine prima. Event listeners pe pointerdown, keydown, touchstart, scroll, plus requestIdleCallback cu fallback setTimeout. Conversion tracking funcționează în continuare pentru că orice click pe tel: e o interacțiune — GTM se încarcă înainte să se declanșeze evenimentul de conversie.

Score după runda 1: 96 / 96 / 100 / 100.

PageSpeed după runda 1: fonts self-hosted + GTM lazy
PageSpeed după runda 1 — fonts și GTM rezolvate.

Runda 2: accessibility 100 și forced reflow

Accessibility 96 → 100. Două probleme. Prima: link-urile de telefon care pe mobile arătau doar SVG-ul iconului (textul cu numărul era ascuns prin CSS la viewport mic) nu aveau nume detectabil de screen readers. Lighthouse marca „Links do not have a discernible name". Am pus aria-label="Call 0800 048 5569" explicit pe fiecare buton de telefon (header, PhoneCTA, sticky bar, footer). SVG-urile decorative au primit aria-hidden="true" și focusable="false".

A doua: lipsa unui focus indicator vizibil pentru navigare cu tastatură. Lighthouse nu marca explicit, dar WCAG 2.4.7 cere. Regulă globală :focus-visible { outline: 3px solid var(--l1-navy); outline-offset: 2px; }. Pe suprafețe închise (header, footer navy), inelul devine galben pentru contrast. :focus-visible în loc de :focus ca să nu apară inelul la click cu mouse.

Forced reflow 280ms → ~10ms. Sticky bar avea un scroll listener care citea window.scrollY și seta dataset.show pe element în același frame. Asta forța synchronous layout la fiecare scroll event. Lighthouse marca 280ms total reflow time per pagină. Am înlocuit scroll listener cu IntersectionObserver care urmărește când headerul iese din viewport. Zero scroll listeners, zero DOM reads, GPU-friendly.

Score după runda 2: 98 / 100 / 100 / 100.

Runda 3: inline CSS

Mai rămăsese un singur warning roșu: „Render blocking requests — 130ms savings". Era un fișier CSS de 13.5KB inclus ca <link rel="stylesheet"> în head — blocant pentru first paint.

Decizia aici cere context, nu rețetă. CSS extern beneficiază de cache între vizite (fișierul cu hash în nume, max-age 1 an în _headers). Inline CSS în HTML duplică acele 13.5KB pe fiecare pagină — la 656 de pagini, ~8 MB extra pe CDN.

Pentru Google Ads trafic în schimb: 95%+ din vizite sunt prima dată. Nu există cache repeat. Câștigul de 130ms LCP pentru toți vizitatorii de prima dată depășește cu mult pierderea pe cei 5% care revin.

Am pus inlineStylesheets: 'always' în astro.config.mjs. Toată CSS-ul a ajuns inline în HTML, compresată Brotli la ~3KB pe wire.

PageSpeed final 98/100/100/100 după 3 runde optimizare
PageSpeed final după 3 runde — 98 / 100 / 100 / 100.

Forced reflow rămas: ce nu am atins și de ce

După toate optimizările, Lighthouse încă marca 74ms forced reflow „unattributed" — fără sursa identificată. Investigat: vine din două locuri. GTM, după ce se încarcă, citește layout-ul pentru tracking de scroll depth și form interactions. Cod terț Google, nu îl pot modifica. Cloudflare Analytics beacon citește performance.timing pentru reporting. Cod terț Cloudflare.

Singura cale de a elimina aceste 74ms ar fi să scot GTM și Cloudflare Analytics. Asta ar însemna să pierd conversion tracking pentru Google Ads (catastrofal) și analytics-ul de bază. Compromis prost. Am acceptat 98 Performance.

9. Schema

4 obiecte schema per pagină.

Chiar dacă site-ul nu țintește SEO organic, Schema.org structured data ajută la Quality Score și la eventual organic traffic care apare oricum.

Pe fiecare pagină de oraș + serviciu:

  • LocalBusiness cu @id, name, image, url, telephone, priceRange, address, areaServed (orașele învecinate ca array), openingHoursSpecification (24/7), aggregateRating
  • Service cu serviceType, provider, areaServed, offers (priceRange, priceCurrency, availability)
  • FAQPage cu cele 6 întrebări specifice paginii
  • BreadcrumbList cu trei nivele

Total: 4 obiecte schema per pagină, toate validate fără warning în Google Rich Results Test.

Pe paginile legale (Privacy, Terms, Cookies), schema LocalBusiness e generată automat printr-un layout shared (StaticPageLayout.astro) ca să nu pierd nici acele 5 pagini din coverage-ul total schema.

10. Tracking

Conversion tracking la nivel de URL.

Aici era un blind spot serios pe site-ul vechi.

Google Ads atribuie conversiile Call la campania care a generat clickul, nu la URL-ul exact al landing page-ului. Asta înseamnă că în „Landing Pages Report" din Google Ads, top 15 pagini după spend au 0 conversii raportate — deși conversiile chiar se întâmplă, doar că datele sunt atașate la nivel de campanie, nu URL.

Pentru un site cu 656 de pagini și 95+ campanii, asta înseamnă că nu poți optimiza pe pagină. Nu știi care pagină convertește, doar care campanie.

Am implementat tracking GTM la nivel de URL pentru fiecare click tel:. Toate butoanele de telefon (header, hero CTA, sticky bar, footer) au handler onclick care face dataLayer.push cu eveniment custom phone_click, plus cta_variant (header/hero/sticky/footer) și page_url.

În GTM se creează un trigger pe evenimentul phone_click care declanșează două tags:

  1. Google Ads Phone Call Conversion (cu conversion ID-ul clientului)
  2. Custom event către Google Analytics 4 (dacă e configurat)

Rezultatul: clientul vede în Google Ads raportul „Landing Pages" exact câte calls vin de pe /locksmith-bath/ față de /upvc-door-repair-bath/ — și poate decide care pagini merită mai mult budget și care trebuie optimizate sau scoase.

Tracking conversie call la nivel de URL prin GTM dataLayer
Flow conversion tracking — click pe tel: → dataLayer → GTM trigger → Google Ads Conversion.
11. Headers

Cache strategy + security headers.

Cloudflare Pages permite definirea cache rules prin fișier _headers în public root. Setarea de cache TTL corectă elimină un warning constant din PageSpeed și reduce request-urile inutile la origin.

Setarea finală:

/_astro/*                     max-age=31536000, immutable
/fonts/*                      max-age=31536000, immutable
/images/*                     max-age=31536000, immutable
/favicon.svg, /favicon.ico    max-age=604800
/*                            max-age=300, s-maxage=3600, stale-while-revalidate=86400

Toate asset-urile cu hash în nume sau filename stabil (/_astro/style-X.css, /fonts/inter-latin.woff2) primesc cache imutabil 1 an. HTML-ul are cache scurt 5 minute (client) dar lung 1 oră pe CDN, cu stale-while-revalidate 24h — Cloudflare servește versiunea veche instant și o reîmprospătează în background, dacă apare deploy nou.

Headerele de securitate adăugate în același fișier _headers:

  • Strict-Transport-Security: max-age=31536000; includeSubDomains
  • X-Content-Type-Options: nosniff
  • Referrer-Policy: strict-origin-when-cross-origin
  • Permissions-Policy: geolocation=(), microphone=(), camera=()

Asta menține Best Practices la 100 pe Lighthouse și protejează utilizatorii.

12. Rezultate

Scorurile finale pe mobile.

Metric Înainte După
Performance4798
Accessibility78100
Best Practices92100
SEO86100
LCP5.1s2.1s
TBT410ms50ms
Page weight (homepage)~1.4 MB~120 KB

Estimat impact pe Quality Score: trecere de la „6.25 average" spre „8.5+ average" în 4–6 săptămâni după lansare. Costul per conversie ar trebui să scadă cu 20-30% pe campaniile high-spend, pe baza unei corelații istorice între Landing Page Experience și CPA.

Comparație before/after — performance și UX optimizate
Comparație finală înainte/după — toate metricile mobile.
13. Lecții

Ce am învățat la acest proiect.

  1. Audit-ul Google Ads precede orice cod. Înainte să mut un singur fișier, am cerut exporturi reale de Campaign, Keyword și Landing Page reports pe 30 de zile. Decizia de a împărți pe 6 template-uri per zonă a venit direct din observația că ad group-urile aveau intent foarte diferit și mergeau toate la aceeași pagină. Fără date, ai construi greșit.
  2. Stack-ul tehnic se alege per tip de trafic. Astro static face sens pentru landing pages Google Ads. WordPress face sens pentru un site care actualizează zilnic conținutul. Next.js cu ISR pentru ceva între. Nu există stack universal bun.
  3. PageSpeed 98 nu se obține din prima. Trei runde de optimizare, fiecare cu diagnostic propriu. Nu există „checklist generic" care să te ducă la 98 — fiecare site are bottleneck-urile lui. Lighthouse e diagnostic tool, nu prescription.
  4. Conflict între best practice și realitate. Cache CSS extern e best practice general. Inline CSS pe fiecare pagină e net positive pentru un site care primește exclusiv first-visit traffic. Best practice generic ≠ best practice pentru tine.
  5. Tracking-ul URL-level pentru calls e subestimat. Toți clienții Google Ads care lucrează la scară pierd această dată. Calls se atribuie la campanie, nu la URL — fără tracking custom în GTM, nu poți optimiza pe pagină. Acesta singur e motiv suficient să refaci un site Google Ads.
  6. Schema markup e gratuit ROI. Două ore de scris JSON-LD pentru fiecare template + un build script care injectează datele per pagină. Răsplata vine în Quality Score (Google citește schema când evaluează relevanța) și în rich snippets organice.
  7. Accessibility 100 cere migală. Nu e mult de făcut, dar trebuie făcut bine. Aria-labels pe link-uri ambigue, focus-visible global, aria-hidden pe SVG decorative. 30 de minute de lucru bine țintit te duce de la 96 la 100.
  8. Forced reflow „unattributed" e câteodată acceptat. GTM și Cloudflare Analytics generează ~70-90ms forced reflow combinat. Singura cale de a-l elimina e să scoți tool-urile. Nu merită. Accepți 98 Performance în loc de 100.
14. Costuri

Cât costă să operezi un astfel de site.

  • Hosting: Cloudflare Pages, 0 lei/lună
  • DNS + domain: domeniu nou la ~50-80 lei/an, DNS prin Cloudflare gratuit
  • Email business: opțional, NameHero sau Migadu, ~30 lei/lună
  • GTM: gratuit
  • Cloudflare Web Analytics: gratuit
  • Build time (CD pipeline): ~5 secunde pentru 656 pagini, deploy ~2 minute
  • Total cost lunar de operare: ~30 lei dacă vrei email pe domeniu, altfel 0

Pentru un client care cheltuiește o sumă mare lunar pe Google Ads, costul de operare al unui asemenea site e neglijabil. Investiția merită doar din economia potențială la CPA.

Stack folosit

Tech stack complet.

  • Frontend: Astro 6, TypeScript, Tailwind CSS 4
  • Build: Vite, output 100% static HTML
  • Hosting: Cloudflare Pages cu edge CDN global (200+ locații)
  • DNS: Cloudflare
  • Analytics: Cloudflare Web Analytics (privacy-friendly)
  • Tag management: Google Tag Manager (lazy-loaded post-interaction)
  • Conversion tracking: Google Ads Phone Call Conversion + custom dataLayer events
  • Schema markup: LocalBusiness, Service, FAQPage, BreadcrumbList per pagină
  • Source control + CI/CD: GitHub → Cloudflare Pages auto-deploy la push

Confidențialitate: numele clientului și URL-ul producției nu sunt dezvăluite. Industria (servicii de urgență la domiciliu UK) și volumul (sumă cu șase cifre lunar pe Google Ads) sunt generale și nu identifică un singur business.


Cheltuiești mult pe Google Ads și simți că pierzi bani?

Dacă rulezi campanii Google Search Ads la scară mare în servicii locale și Landing Page Experience te trage în jos, scrie-mi. La primul apel discutăm exporturile reale din contul tău și math-ul recovery-ului.

Aplică pentru o discuție →