Case study · Redesign, SEO, performance & services architecture Live in production · 30 July 2026 See the rebuild → View the live site →

Supreme Nomads Detailing

A Charlotte mobile-detailing site that looked good and had the content — but served one document across forty-six URLs, and led with a sentiment instead of the service. Rebuilt front to back: a redesign that answers the first question a visitor asks, and an SEO layer underneath so every page can be found on its own terms.

Client
Supreme Nomads Detailing · Charlotte, NC
Scope
Redesign · SEO audit · Per-page metadata · Structured data · Performance · Services architecture
Stack
React 19 · Vite · React Router 7 · Tailwind · Sanity CMS · Netlify
Studio
Tenworks · Tenzin
Two halves, one rebuild

The visible half is a redesigned front end — a headline that says what the business does and where, pricing and reviews above the fold, services split into pages you can browse, and Reviews in the nav.

The invisible half is what search engines are handed: per-page titles, descriptions and self-referencing canonicals stamped into the served HTML at build time, a richer schema layer, and the performance dead weight removed. Both halves shipped together and went live on 30 July 2026. Sections 01–10 describe the build, not a ranking result — a Search Console baseline was captured at launch so there would be an honest before. Sections 11–14 are that measurement, plus the work that continued after launch: the search data two weeks in, the pricing and content the owner changed, and the three SEO problems the live site produced. Every number on this page is measured, not projected.

01 · The starting point

One page wearing forty-six addresses.

The site had real content — twenty-five location pages, service copy, a business schema graph, a clean sitemap. But the React app had routing and no head management, so every route served the homepage's title, description and canonical. To Google, the About page, every service and all twenty-five city pages were the homepage. On screen, the hero answered none of the questions a first-time visitor actually has.

02 · The numbers

Counted, not estimated.

Both sitemaps were read, every URL in each was fetched and hashed, and titles and canonicals were parsed from the raw HTML — the version a crawler sees, not the rendered page.

1 → 53
distinct documents across the rebuild's sitemap
1 → 53
unique titles and meta descriptions
46 → 53
self-referencing canonicals instead of forty-six pointing at the homepage
2.88 MB
unused render-blocking CSS removed from every page

The previous site: 46 URLs, 46 responses of 200, one distinct hash. The rebuild, now live: 53 URLs, 53 responses of 200, 53 distinct hashes. Figures measured 29 July 2026, before launch.

03 · The redesign

The homepage now answers the first question.

Someone landing from a search has one question — do you do the thing I need, near me, at a price I can live with. The old hero answered none of it.

BeforeThe current Supreme Nomads homepage — a centred sentiment headline over a photo of a detailed grille
“Our top priority is you.” No service, city, price or reviews.
AfterThe rebuilt Supreme Nomads homepage — Mobile Auto Detailing in Charlotte, with pricing, two buttons and a review line
“Mobile Auto Detailing in Charlotte.” Service, city, from $125, two actions, 5.0 reviews.

One services page became a structure.

Detailing, ceramic coating, paint protection and the add-ons were all on a single URL. Nothing on one page can rank for four different searches.

BeforeThe current Supreme Nomads services page — everything on one URL
Everything on one URL — and the homepage's title and canonical, like every other page.
AfterThe rebuilt Supreme Nomads services page — categories with from-prices
Categories with from-prices, each linking through to a page that can rank for its own search.
AfterA Supreme Nomads service detail page for detailing packages
Each detail page — its own title, description, canonical and Service schema node.

See the old site as it was, captured before the rebuild →

04 · The SEO foundation

Every page given its own identity — in the HTML.

A single-page app renders its content in the browser, after the crawler has already left. The fix was two-fold: give every route its own metadata, then bake that metadata into the HTML that's actually served — no JavaScript required to read it.

1 → 53
pages, each with a unique title, description and self-canonical
Every route
mapped with hand-written, keyword-aware metadata
0
new dependencies — built on React 19's native metadata
Static
head tags prerendered into the served HTML at build time

A central route→metadata map now drives a unique title, description and canonical for every page. A post-build step writes those tags straight into each page's HTML file, so search engines and social crawlers get the real thing on first byte — while the app still owns the head at runtime, with no duplicate tags. The blog stub was set to noindex, and the Open Graph image moved to an explicit JPEG (f_jpg) instead of a .heic URL relying on Cloudinary's f_auto conversion.

05 · Structured data

Made machine-legible — page by page.

The live site already had a business graph — LocalBusiness, address, phone, fifteen cities, social profiles. What it lacked was anything that described individual pages. People also ask ChatGPT, Gemini and Perplexity “who does mobile detailing near Charlotte?” — and those systems answer from sites they can parse.

The rebuild keeps the business graph and extends it: opening hours and a six-package offer catalogue with real prices. Every service page now carries its own Service + Offer node — provider, price, and the fifteen Charlotte-area cities it serves — so an answer engine has real facts to quote instead of guesses.

LocalBusiness graphOpening hourshasOfferCatalog · 6 packagesService + Offer per pageareaServed · 15 citiesSelf-canonical everywhere
A deliberate distinction

Review-star markup was left off on purpose — Google no longer surfaces rich results for self-serving rating markup, and misusing it risks a manual action, so the schema stays clean rather than chasing stars that won’t show. FAQ markup, by contrast, is included on the location pages — but as answer-engine optimisation, not a rich-result gamble. Google won’t render FAQ snippets for a local business, yet ChatGPT, Gemini, Perplexity and Google’s AI Overviews read structured FAQ data to quote answers — so the FAQ schema earns its place for AEO, exactly the “answer from sites they can parse” logic above.

06 · Performance

Stopped rebuilding the site on every click.

The biggest speed cost wasn't the first load — it was every load after it. The app was re-injecting its entire vendor stack on each navigation.

2.88 MB
unused stylesheet removed from every page
7,600-line
plugins bundle now loads once, not per navigation
Once
jQuery, Bootstrap, Swiper, AOS and Iconify loaded a single time
Lazy
below-the-fold images deferred with native lazy-loading

Vendor libraries were centralised to load exactly once and re-initialise per-page widgets without re-downloading anything; square.css was removed; below-the-fold images now lazy-load and async-decode while hero images stay eager to protect LCP. Fonts are deferred and the hero image is preloaded at high priority. Two UX bugs went with it: navigating to a new page now resets scroll to the top instead of landing mid-page, and the mobile hero video — a classic React muted-property bug — now autoplays.

Then, after launch — the mobile Core Web Vitals.

With the rebuild live, a second pass went straight at mobile load speed, guided by Google’s own Lighthouse. Interactivity and layout stability were already perfect — this was pure load-time.

68 → 74
mobile Lighthouse performance score
6.6s → 4.7s
Largest Contentful Paint
10 ms
Total Blocking Time — already excellent
0
Cumulative Layout Shift

Google Tag Manager was deferred off the critical path; every icon was bundled offline to remove five-plus render-blocking requests to a third-party CDN; the live-reviews function was edge-cached so a ~1.5 s API round-trip stopped gating render; and the hero poster was made responsive for phones. Measured on a throttled Moto G with Lighthouse 13.4.

07 · Services architecture

One services page became fifteen.

One page trying to rank for detailing, ceramic coating, paint protection and nine add-ons ranks for none of them. The rebuild split services into a data-driven architecture where each offering earns its own URL.

1 → 15
service URLs, each with its own title, schema and copy
4
category pages with vehicle-size pricing tabs
1
source of truth — all copy and pricing in one data file
Cross-linked
every service tied to the cities it serves

A single services data file now defines four categories, their priced tiers and eleven individual detail pages. Category pages present packages by vehicle size; detail pages lead with a keyword H1 (“{service} in Charlotte”), the inclusions, price, an FAQ, and links to related services and nearby cities. The sitemap and structured data generate straight from that one file — add a service, and its page, schema and sitemap entry follow automatically.

08 · What a crawler is handed

The invisible half, on /services/detailing.

This is the raw HTML before any JavaScript runs — the version Google indexes. Left is live today. Right is the rebuild.

Live today · identical on all 46 URLs

  • TitleSupreme Nomads Detailing - Charlotte - Mobile Auto Detailing — the homepage's title, on a detailing packages page.
  • Canonicalhref="https://www.supremenomads.com/" — tells Google this page is really the homepage. On all 46 URLs.
  • DescriptionOne homepage description doing duty for forty-six pages.
  • Stylesheet/square.css — 2.88 MB, render-blocking, unused by the design on screen.
  • Page schemaNo Service node for this page — nothing in the markup says what this page sells.

The rebuild · unique on all 53

  • TitleMobile Detailing Packages in Charlotte, NC | Supreme Nomads — written for this page, stamped in at build time.
  • Canonicalhref="https://www.supremenomads.com/services/detailing" — points at itself. All 53 routes canonical to themselves.
  • Description53 routes, 53 distinct descriptions.
  • StylesheetThe 2.88 MB file is gone; hashed CSS bundle only.
  • Page schemaA Service JSON-LD block keyed to this page, alongside the business graph.
09 · What changed

Before → after, line by line.

Pages
46 URLs, 1 document53 URLs, 53 documents

Every URL returns its own HTML file, not a copy of the homepage.

Homepage hero
A sentiment, one arrowService, city, price, reviews

“Mobile Auto Detailing in Charlotte” with from $125, two buttons, and the 5.0 count above the fold.

Indexability
One homepage title on every URL53 unique, prerendered pages

Search engines see every page as itself, in the served HTML.

Structured data
Business graph only, sitewideBusiness graph + per-page Service nodes

Opening hours, offer catalogue, and a Service node on every service page.

Services
1 page15 URLs

Every service and category built to rank on its own terms.

Performance
Full stack re-run per clickloaded once

Plus 2.88 MB of unused CSS removed from every page.

Locations
Unindexable25 local landing pages

Each town rebuilt with a local hero, reviews, services, map and FAQ — and declared in schema with its own geo-coordinates.

Reviews
Not in the navOwn nav item + hero count

Social proof has a home instead of being buried.

Social preview
.heic via f_autoexplicit JPEG

Cloudinary was already converting on the live site; the rebuild uses an explicit f_jpg URL.

Mobile hero
Dead videoautoplays

The first thing a phone visitor sees now moves.

Blog SEO
Manual / missingself-managing

Future CMS posts arrive SEO-complete with no code.

Core Web Vitals
Mobile 68 · LCP 6.6sMobile 74 · LCP 4.7s

A post-launch pass: deferred analytics, offline-bundled icons, an edge-cached reviews call and a responsive hero poster.

Gallery
Placeholder grid49 real vehicles

Ten customer cars, delivered responsively through Cloudinary.

Booking
One generic shop linkdeep-linked by category

“Book Now” opens the right Urable category directly.

Email privacy
Plain-text mailtoobfuscated

No scrapable address in the served HTML.

10 · Launch & after

Shipped — and tracked from here.

The rebuild is live. A short list carried over the launch line — none of it blocked the deploy, and the measurement chapter starts now.

What's next — the numbers

The foundation is written; the next chapter is measurement, and it hasn't happened yet. Nothing on this page is a ranking result.

At deploy, a Search Console baseline gets captured: indexed page count, the “duplicate — Google chose a different canonical” bucket, and impressions by query. Indexing coverage and ranking movement are then tracked against that baseline over the following weeks. That's the honest way to report SEO — once the data exists, rather than before it.

11 · The measurement

The chapter the launch page promised.

Section 10 ended on a line: “Nothing on this page is a ranking result.” A Search Console baseline was captured at launch on purpose, so there would be an honest before. This is the after — just over two weeks of live search data, measured against that frozen baseline.

7 → 23
clicks from Google, per tracked window
628 → 1,437
search impressions
82 → 167
distinct searches the site appears for
~7 → ~40
pages earning impressions on their own

Before launch Google saw one page — the homepage — and showed it for a handful of searches, nearly all of them people already typing the business name. Two weeks after launch the site was appearing for twice as many searches, dozens of individual pages were ranking by themselves, and for the first time people who weren't searching the brand were clicking through.

The neighbourhood pages woke up.

The whole point of the rebuild was that the old site had one page wearing forty-six addresses. After launch, individual pages started ranking for their own searches: the Ballantyne page climbed to position 3–4, Lake Wylie began showing for “lake wylie detailing,” and service pages like stain removal and trim restoration surfaced on page one. Searches appeared that had never existed for this site before — “ceramic coating concord nc,” “mobile detailing huntersville nc,” “detail shop near me.” That is the long tail the architecture in section 07 was built to catch.

The first non-branded click

Someone searching “headlight restoration near me” found the site at position 4 and clicked. Small in absolute terms, but it is the thesis in one line: the site started earning strangers, not just people who already knew the name.

One caveat changes how the whole table should be read, and it is the subject of section 13: nearly all of that growth happened while two dozen of the neighbourhood pages were still blocked from the index entirely. The foundation was doing its job before its main asset had come online.

12 · After the launch

The part that isn't a launch — it's the weeks after.

A rebuild going live is the easy story. What follows is a real engagement: the owner restructures pricing, sends corrections by text, notices something on his phone that looks wrong. None of it is glamorous, and all of it is what keeps a site accurate enough to be worth ranking.

Measured, not assumed.

One piece of that work is worth showing the method for. After the gallery rebuild the owner noticed photos took a moment to appear when stepping through the lightbox. Every arrow click was starting a fresh full-size download with nothing warmed and nothing on screen in the meantime. The fix: preload the whole job when the lightbox opens, and show a roughly 1 KB blurred stand-in so the frame is never empty.

It was then measured rather than declared — a local origin serving real images behind an artificial 700 ms delay, driven by a real browser.

~700 ms → ~150 ms
to show the next photo in the lightbox
~175 ms
until something is on screen, from a cold open
0
photos downloaded twice

The first two attempts at that measurement showed no improvement at all — the test harness was disabling the browser cache the fix depends on. Worth saying out loud: the honest number took three tries, and the first two would have been wrong in the flattering direction.

13 · The SEO layer, after launch

Building it was section 04. This is keeping it true.

Sections 04 and 05 built the SEO layer: per-page titles, descriptions and self-referencing canonicals stamped into the served HTML at build time, plus a structured-data layer on every page. Building that is a one-off. Keeping it correct while a live business changes its prices, adds services and gets crawled by Google is the actual work — and in the first weeks it produced three separate problems, only one of which Google bothered to report.

One: 24 pages blocked, silently.

Two weeks in, the coverage report showed 24 pages — almost all of them the neighbourhood pages — stuck on “Redirect error.” Nothing looked broken to a human; every one of them loaded fine in a browser.

The cause was structural. Pages are built as <route>/index.html, so Netlify serves them at the trailing-slash URL and 301s the version without one — but the sitemap and the canonical tags both used the no-slash form. Googlebot was being sent to a URL that redirects, landing on a page whose canonical pointed straight back at the redirecting URL. A loop, from the crawler's point of view, and an invisible one from ours.

The fix was to make every layer agree on one form. Five files, one rule: sitemap generation, build-time canonical injection, the runtime canonical component, blog-post canonicals, and the @id in the location schema. Verified live afterwards — the worst-case URL, http://…/locations/concord with no slash and no TLS, now resolves to the canonical page in a single hop.

Two: the sitemap was perfect. Three pages weren't.

A routine audit of the live sitemap: 56 URLs, every one https + www + trailing slash, no duplicates, no orphans, valid dates, robots.txt allowing everything and pointing at it — and matching exactly what the code generates from the services and locations data. By every check a sitemap validator runs, it passed.

Then the pages themselves were opened. Three of them — the newest add-on services — were serving the homepage title, the homepage description, and a canonical pointing at the homepage. A page that declares itself a duplicate of the homepage will not be indexed, no matter how many times it is submitted.

24
pages unblocked by the canonical fix
56 / 56
sitemap URLs valid and correctly formed
3
of them pointing their canonical at the homepage
0
automated checks that would have caught it

The cause is worth naming, because any site built this way can fall into it. The sitemap is generated from the services and locations data. The per-page SEO — titles, descriptions, canonicals — comes from a separate map, and that same map doubles as the route list for prerendering. Add a service and the first source updates, so the page appears in the sitemap immediately; miss the second and the page has no SEO identity at all, is never prerendered, and its canonical silently falls back to the homepage. Two lists, one source of truth each, drifting apart with nothing failing.

Three: make it not happen again.

Why this section is in the case study

The three-page canonical gap was our own. It would be easy to leave out — it was found in our own work, before it cost anything.

It stays in because it is the actual argument. A site is not finished when it deploys. The failures that matter afterwards are the silent ones: nothing 404s, nothing errors, the page looks perfect to the person who built it, and Google quietly declines to index it. The only thing that catches that is someone still looking.

14 · What changed, after launch

The second pass, line by line.

Canonical URLs
No-slash, redirectsTrailing slash, one hop

Sitemap, canonicals and the server now agree. Removes the cause of the 24-page “Redirect error.”

Per-page SEO coverage
3 pages on the homepage canonical56 / 56 covered

Verified site-wide: no missing entries, no accidental noindex, no duplicate titles or descriptions.

Sitemap
Unaudited since launch56 URLs, all verified

Generated from the services and locations data, so it stays in sync as the business adds work.

Indexing
Submit and hopeTiered, sequenced plan

Deploy, verify the live canonical, then request — in priority order, within the daily quota.

Pricing
Pre-restructureReconciled line by line

Six detailing tiers × three vehicle sizes, three paint options, two plans, twelve add-ons — every price, duration and bullet checked against the owner's sheet.

Add-ons
1012

Carpet extraction and headliner spot cleaning built as full service pages with their own pricing, FAQs and schema.

Package comparison
Two inherited errorsMatches the source data

Headliner cleaning removed from the included list, and windows & door jams unticked for the Exterior tiers.

Reviews
8 hardcoded29 with photos

Verbatim, four photos each. The eight were a fallback the site had been stuck on since the review API token expired.

Gallery
49 loose tiles10 jobs, grouped

One Cloudinary folder is one job; the shop adds work by dropping in photos, with no deploy.

Lightbox
~700 ms per photo~150 ms

Whole job preloaded on open, blurred stand-in while the full image decodes. Measured, not estimated.

Mobile comparison table
Redundant stacksHidden below the breakpoint

Owner request: on a phone it repeated the package cards directly above it.

Repository
Dead files with old prices~380 KB removed

Including two unreferenced files still carrying pre-restructure pricing.

Academy quote form
Fixed fieldsFree-text enquiry

People asking about training can say what they actually want.

What this page is not claiming

The search numbers are early and small in absolute terms. This is one detailing business in one city — dozens of clicks, not thousands. The story is direction, not size.

It is correlation, not a controlled test. The launch is the most likely driver of the lift, but two weeks of a young site is not a lab, and no algorithm update has been ruled out.

The big head terms still convert nothing. The site ranks first for “auto detailing” and “mobile detailing,” but the local map pack sits above the results and takes the click. That is a Google Business Profile lever, not an on-page one.

On-site analytics were excluded. Bot and staging traffic made the visit counts unreliable, so everything here comes from Search Console, which measures what actually happened on Google. If a number couldn't be trusted, it isn't on this page.

Google Search Console, organic Search Launch baseline 29–30 Jul 2026 Measured ~2 weeks after Directional, not a controlled experiment
See it live

The rebuild shipped to production.

Live on the client's own domain since 30 July 2026 — the redesigned front end and the SEO layer underneath, on every page.