Illustrative case study

A fictional business. The figures are a modelled scenario, not a client result.

Ceruvia Cosmetics

Clean makeup brand · Buenos Aires · Beauty & Fashion

Fixing a beautiful headless store that search engines couldn't see

+268%organic sessions recovered and grown

6-month engagement · Signal (Technical SEO) · Latin America

Mirror · launch day
The top mirror shows the launch-day page as Googlebot received it: an empty, blurred shell. The base reveals the server-rendered rebuild.
01The challenge

The mirror showed a beautiful store. Google saw an empty case.

A headless rebuild looked stunning but rendered product content client-side, and organic traffic fell 46% within a month of launch.

Ceruvia's new storefront was a design triumph: fluid shade swatches, a virtual try-on, editorial product stories. But every word of it was assembled in the browser. Search engines fetched a blank <div>, queued the JavaScript for later and, for hundreds of shades, never came back. Best-sellers that had ranked for years slipped off page one within weeks, and the paid budget was quietly covering the hole.

Organic sessions / monthLaunch: −46%
0k15k30k45kBeforeLaunchM1M2M3M4M5M611,58042,610 (+268%)
02The diagnosis

Look past the reflection. Read the raw HTML.

We crawled 4,100 URLs twice, once as raw HTML and once fully rendered, and compared them line by line against three months of server logs. The gap between the two crawls was the whole story.

View source · Googlebot first fetch
<!doctype html>
<html lang="es">
<head>
  <title>Ceruvia</title>
  <script src="/static/app.3f9c.js" defer></script>
  <!-- 3.1 MB of JavaScript -->
</head>
<body>
  <div id="root"></div>
</body>
</html>

0 products · 0 links · 3.1 MB JS

  1. 01 · RenderingCritical

    88% of URLs shipped an empty shell

    Product, collection and shade pages returned <div id="root"></div> and a 3.1 MB bundle. Titles, prices and copy only existed after hydration, so the first crawl pass saw nothing to rank.

  2. 02 · IndexingCritical

    560 products stuck in 'Discovered – not indexed'

    Variant links were rendered by JavaScript click handlers, not <a href>. Googlebot found 640 product URLs in the sitemap but only 80 had crawlable internal links.

  3. 03 · PerformanceHigh

    Mobile LCP of 6.2 s on the shade finder

    The hero swatch image was lazy-loaded behind a client-side fetch, fonts blocked rendering and the full catalogue JSON was parsed on every page.

  4. 04 · Structured dataHigh

    Product schema injected after load, half of it invalid

    JSON-LD was written by a tag manager, missed offers on 70% of SKUs and duplicated brand entities, so no shade earned price or review rich results.

  5. 05 · RoutingMedium

    Canonicals pointed every shade at the parent

    A single canonical template collapsed 18 foundation shades into one URL and filtered collection URLs returned 200 with identical content.

  6. 06 · MigrationMedium

    212 legacy URLs without redirects

    The old Tiendanube paths for best-sellers and blog guides returned soft 404s, discarding years of earned links.

03The strategy

Four layers, applied in order, like a good base.

Built on our Signal plan: we kept the headless stack and the design, and changed only what the crawler receives.

  • No. 01 · Foundation

    Headless commerce

    Kept the headless storefront the brand loved, but moved it onto a server-first architecture so every route answers with complete HTML.

    • Product and collection routes server-rendered from the commerce API
    • Incremental static regeneration for 640 PDPs, revalidated on stock change
    • 212 legacy paths mapped with 301s at the edge
  • No. 02 · Primer

    JavaScript rendering

    Removed the dependence on Google's deferred rendering queue: what the crawler sees on the first fetch is what shoppers see.

    • Variant and shade links converted to real <a href> anchors
    • Hydration split so reviews and the try-on widget load as islands
    • Rendered-HTML diff checks added to every deploy
  • No. 03 · Setting powder

    Core Web Vitals

    A performance budget per template, enforced in CI, so the store stays as fast as it looks.

    • Hero swatch preloaded with fetchpriority=high and AVIF fallbacks
    • JavaScript per PDP cut from 3.1 MB to 290 kB
    • System font fallback with size-adjust to remove layout shift
  • No. 04 · Highlighter

    Structured data

    Schema rendered on the server from the product source of truth, never by a tag manager after load.

    • Product, Offer and AggregateRating on every shade variant
    • ProductGroup with hasVariant for 18-shade ranges
    • Organization and BreadcrumbList unified across the site
04Month by month

A 6-month palette, one pan at a time.

  1. 1

    Month 1

    Audit & triage

    Rendered-vs-raw crawl of 4,100 URLs, log-file analysis, 212 redirects shipped in week two to stop the bleed.

  2. 2

    Month 2

    Server rendering

    PDP and collection templates moved to SSR. Server-rendered share jumps from 12% to 61%.

  3. 3

    Month 3

    Crawl paths

    Shade links become anchors, faceted URLs canonicalised, XML sitemaps split by type. Indexed products pass 300.

  4. 4

    Month 4

    Speed budget

    Bundle split, hero preload, font fallback. Mobile LCP drops below the 2.5 s threshold for the first time.

  5. 5

    Month 5

    Rich results

    Server-side ProductGroup schema goes live; price and rating snippets appear on 520 shades.

  6. 6

    Month 6

    Compound

    100% of templates server-rendered, 640 products indexed, regression checks wired into every release.

05The results

Same store. Finally visible.

Modelled scenario for a fictional business — not a client result. Rankings and traffic are never guaranteed; see our Terms.

  • Pages server-rendered12% → 100%
    100%
  • Indexed products80 → 640
  • Mobile LCP6.2s → 1.5s
    6.2s1.5s0s2.5s4s7sgood
  • Organic revenue+154%

Shades back on page one

Google Argentina positions for priority queries, measured from a Buenos Aires location in the month before the engagement and again in month 6. A dash means the page was not in the top 100.

Keyword rankings before and after
QueryMonthly volumeBeforeMonth 6
base de maquillaje vegana6,600—2
clean makeup argentina2,900381
corrector natural tono medio2,400—3
labial mate sin crueldad1,900544
rubor en crema orgánico1,300—2
ceruvia skin tint shade 06720711
maquillaje limpio buenos aires590221

Your store, seen clearly

Get a plan like this

If your storefront looks right but ranks wrong, start with Signal: a rendering audit, a prioritised fix list and the engineering hours to ship it.

← All case studies