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.
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.
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.
- Every URL served the homepage's title and canonicalThe sitemap declares 46 URLs. All 46 return the byte-identical HTML file — one title, one description, one canonical pointing at the homepage on every route.
- No per-page metadata at allNo unique titles or descriptions for services, locations or the blog — the exact fields that decide what shows in a search result.
- Schema existed, but nothing described individual pagesA LocalBusiness + Organization graph was present sitewide — address, phone, fifteen cities, social profiles. Good foundation, but identical on every URL with no Service node for any service page.
- A 2.88 MB stylesheet on every page
/square.css— a leftover marketplace-theme file the design on screen never used, render-blocking on every load. - A 7,600-line script bundle re-running on every clickjQuery, Bootstrap, Swiper, AOS and a large plugins file were re-downloaded and re-executed on every in-app navigation.
- Every service crammed onto one pageDetailing, ceramic coating, paint protection and nine add-ons all lived on a single
/servicesURL, so none could rank for its own search. - The hero led with a sentiment, not the service“At Supreme Nomads Detailing, our top priority is you” — no service named, no city, no price, no reviews, and a single scroll arrow as the only action.
- The hero video was dead on mobileOn phones the homepage video never played, leaving the single most important screen static.
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.
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.
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.


- The headline is now the search someone typed“Mobile Auto Detailing in Charlotte” is what people actually type — and it is now the first thing on the page, serving the visitor and the search engine with the same words.
- Price and proof stopped being a scroll away“Premium on-site detailing — from $125” sits directly under the headline, with a five-star line and the review count. Someone who needs to know whether this is a $60 wash or a $900 coating finds out immediately.
- Two ways in, not one arrow“Book Your Detail” for someone ready, “See Pricing” for someone still deciding — and Reviews added to the navigation so social proof has somewhere to live.
- The About page, rebuiltA flat, dated page became an on-brand story: a credibility stats band, tightened copy, the two founders, and a testimonials row pulled from real Google 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.



Service schema node.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.
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.
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.
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.
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.
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.
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.
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.
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.
- 25 location pages rebuilt into local landing pagesEvery city and neighbourhood page — already written with genuinely local copy — was rebuilt on the design system: a city-specific hero, live Google reviews, service cards with from-pricing, a photo strip of real work, a service-area map, nearby-city links and an on-page local FAQ. Each got its own metadata plus a per-city
LocalBusinessnode with real geo-coordinates, and every town is declared in the serviceareaServed— one component drives all 25. - The gallery went live with real workThe placeholder grid became 49 real customer vehicles across ten cars, delivered responsively through Cloudinary and lazy-loaded below the fold.
- “Book Now” deep-links by categoryInstead of one generic shop link, each booking button opens the correct Urable category directly — detailing, ceramic coating or paint protection — through a mapped
?cat=parameter. - The blog now handles its own SEOPosts come from a Sanity CMS. Each post generates its own title, description and canonical from the fetched content — so every future post the owner publishes is SEO-complete automatically, no developer in the loop.
- A sitemap that maintains itselfThe XML sitemap is generated from the same data files at build time, so new services and pages appear in it without anyone remembering to.
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
- Title
Supreme Nomads Detailing - Charlotte - Mobile Auto Detailing— the homepage's title, on a detailing packages page. - Canonical
href="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
- Title
Mobile Detailing Packages in Charlotte, NC | Supreme Nomads— written for this page, stamped in at build time. - Canonical
href="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
ServiceJSON-LD block keyed to this page, alongside the business graph.
Before → after, line by line.
- Pages
- 46 URLs, 1 document→53 URLs, 53 documents
Every URL returns its own HTML file, not a copy of the homepage.
- Homepage hero
- A sentiment, one arrow→Service, 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 URL→53 unique, prerendered pages
Search engines see every page as itself, in the served HTML.
- Structured data
- Business graph only, sitewide→Business graph + per-page Service nodes
Opening hours, offer catalogue, and a Service node on every service page.
- Services
- 1 page→15 URLs
Every service and category built to rank on its own terms.
- Performance
- Full stack re-run per click→loaded once
Plus 2.88 MB of unused CSS removed from every page.
- Locations
- Unindexable→25 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 nav→Own nav item + hero count
Social proof has a home instead of being buried.
- Social preview
- .heic via f_auto→explicit JPEG
Cloudinary was already converting on the live site; the rebuild uses an explicit
f_jpgURL. - Mobile hero
- Dead video→autoplays
The first thing a phone visitor sees now moves.
- Blog SEO
- Manual / missing→self-managing
Future CMS posts arrive SEO-complete with no code.
- Core Web Vitals
- Mobile 68 · LCP 6.6s→Mobile 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 grid→49 real vehicles
Ten customer cars, delivered responsively through Cloudinary.
- Booking
- One generic shop link→deep-linked by category
“Book Now” opens the right Urable category directly.
- Email privacy
- Plain-text mailto→obfuscated
No scrapable address in the served HTML.
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.
- One leftover
.heicreference on /academyFifty-two routes are clean;/academystill calls the old.heicin a CSS background. Looks like a single missed reference rather than anything structural. - The keywords meta tagStill on the new build. No major search engine has read it in over fifteen years — harmless, but dead weight in every page's head.
- Keep the preview out of the indexThe develop deploy answers publicly and sends no
X-Robots-Tag. The self-referencing canonicals point at the production domain, which protects you, but anoindexheader on branch deploys would remove the question entirely. - Search Console baseline — captured at launchIndexed page count, the “duplicate — Google chose a different canonical” bucket, and impressions by query were recorded on launch day, so the improvement has something to measure against.
- The latest round is rolling outThe Core Web Vitals pass, the location-page redesign, the live gallery and category-deep-linked booking are built and verified on the staging build, deploying to production in the same window.
- Resubmit the sitemap after deployIt grows from 46 URLs to 53, and the 46 that already existed change what they say about themselves. Worth prompting a recrawl rather than waiting.
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.
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.
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.
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.
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.
- Pricing rebuilt against the owner's sheetA restructured pricing sheet arrived and the site was reconciled against it line by line: six detailing tiers across three vehicle sizes, three paint-protection options, two maintenance plans and twelve add-ons — every price, every duration, every bullet. Two paint-protection tiers collapsed into a single Supreme Nomads Special. Ceramic coating's listed turnaround moved from “6–12 hrs” to “1–2 days” because that is what the shop actually does.
- The add-on menu grew from ten to twelveCarpet extraction and headliner spot cleaning became real services with their own pages, pricing, FAQs and matched icons — not line items buried in a list. Each new page inherits the same per-page metadata and Service schema every other service page gets.
- The comparison table stopped lyingThe owner caught two errors we had shipped: headliner spot cleaning was still shown as included in two packages after it became a paid add-on, and windows & door jams were ticked for the Exterior tiers when those packages only cover exterior glass. Both corrected against the source data.
- Reviews went from eight to twenty-nineThe live site had been falling back to eight hardcoded reviews because the Google API token behind them had expired. Twenty-one more were added verbatim — never paraphrased, never “improved” — each with four photos from the original review.
- The gallery was rebuilt around jobs, not photosForty-nine loose tiles became ten job cards, one per vehicle, each opening a lightbox holding that job's full set. One Cloudinary folder is one job, so the shop adds work by dropping photos in a folder — no code, no deploy.
- Mobile got what mobile actually needsThe package comparison table is genuinely useful on a desktop and redundant on a phone, where it collapses into stacks that repeat the cards directly above it — so at the owner's request it is now hidden below the table breakpoint. The sticky vehicle-size selector was reworked to blend into whatever section it sits in.
- Dead weight removedRoughly 380 KB of verified-unused files taken out of the repository, including two files still carrying the pre-restructure prices — the exact class of stale data that had just caused a correction.
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.
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.
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.
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.
- Found in audit, not in a reportThe three pages were caught before Google acted on them — the difference between a fix and a recovery.
- Fixed at the source, then provedProper titles, descriptions and self-referencing canonicals written for all three, then cross-checked programmatically across the whole site: no sitemap URL missing an SEO entry, none marked noindex, no duplicate titles, no duplicate descriptions.
- Written down as a ruleAdding a service or a city is now documented as a two-file change, with a one-line audit command that fails loudly if the sitemap and the SEO map ever drift again.
- An indexing plan, not a buttonManual indexing requests are quota-limited, so the pages are tiered — core, then top service and primary location, then the local money pages, then the long tail — with the sitemap left to crawl the rest. Every URL is listed in the trailing-slash form so it matches its canonical exactly.
- Sequenced correctlyThe canonical fix is a build-time change: it does nothing until a deploy. Requesting indexing before that ships would have burned the quota on pages still serving the wrong canonical — so the order is deploy, verify live, then request.
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.
The second pass, line by line.
- Canonical URLs
- No-slash, redirects→Trailing 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 canonical→56 / 56 covered
Verified site-wide: no missing entries, no accidental noindex, no duplicate titles or descriptions.
- Sitemap
- Unaudited since launch→56 URLs, all verified
Generated from the services and locations data, so it stays in sync as the business adds work.
- Indexing
- Submit and hope→Tiered, sequenced plan
Deploy, verify the live canonical, then request — in priority order, within the daily quota.
- Pricing
- Pre-restructure→Reconciled 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
- 10→12
Carpet extraction and headliner spot cleaning built as full service pages with their own pricing, FAQs and schema.
- Package comparison
- Two inherited errors→Matches the source data
Headliner cleaning removed from the included list, and windows & door jams unticked for the Exterior tiers.
- Reviews
- 8 hardcoded→29 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 tiles→10 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 stacks→Hidden 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 fields→Free-text enquiry
People asking about training can say what they actually want.
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.