Skip to content
MUNEEB SHAFIQ
WEB / SEO ENGINEERINGS08

ElitePick AI

A prerendered Vite SPA, audited against its own search data and cut from thirty pages to three.

  • Google Indexing API
  • Node.js build tooling
  • TypeScript
  • React 18
Status
Active and deliberately small. Built between 2026-01-21 and 2026-02-28 across 27 commits; audited and cut to three pages in September 2026. Live at elitepickai.com.

By the numbers · 6

  • 3

    Routes prerendered, each with its own title and self-canonical

  • 26

    Routes that previously shared a single title and canonical

  • 72

    Fabricated or unverifiable claims removed

  • 27

    URLs retired behind 301 redirects

  • 10,925

    Lines deleted against 1,019 added

  • 7

    Security headers applied at the edge

Summary

ElitePick AI is the site for an independent data and AI practice: a Vite single-page application on Vercel that a post-build script rewrites into real HTML, so a client-rendered SPA is readable by crawlers. It was built that way in early 2026, and in September it was audited against what it was actually doing in search. Two things were wrong, and both had the same shape: a mechanism that looked like it worked and was never checked. The prerender replaced each route's body but never its head, so all 26 generated pages shipped the homepage's title and a canonical pointing at the root, and Google consolidated thirty URLs into one. And the content was generated marketing copy nobody maintained, carrying 72 claims that were either invented or impossible for a reader to check. The site is now three pages, each with its own head, and every figure on it links to somewhere it can be verified.

The problem

An independent data and AI practice that relied on freelance marketplaces for work had no destination of its own, so the first version of this site was built to rank for narrow, high-intent searches on static hosting with no server, no editor and no content team. It was then left alone for six months, which is where the real problem started. The generated marketing copy that filled thirty pages was never going to be maintained by one person with a full-time job, and nothing in the build verified that the SEO machinery was doing what it appeared to do. By September the site averaged a search position deep enough that its impressions almost never became clicks, and the reason turned out to be a single unchecked string substitution.

Approach

  1. Content is modelled as typed TypeScript modules, so page routes, navigation, the sitemap and the pre-rendered HTML all derive from one in-repo source instead of a CMS.

  2. scripts/prerender.mjs runs after vite build and rewrites dist/<route>/index.html for each route, replacing the empty root div with hand-authored semantic HTML so crawlers receive real content from a client-rendered SPA.

  3. The defect that survived six months: that script performed exactly one substitution per route, on the body, and never touched the head. react-helmet-async did set the correct per-page tags, but only in a browser once the bundle had run, which is no help to a crawler reading raw HTML. Every generated page therefore declared the homepage as its canonical, and thirty URLs were consolidated into one.

  4. It survived because a failed String.replace is indistinguishable from a successful one: it silently returns its input. The fix is a replaceOnce helper that throws unless a pattern matches exactly once, so a moved tag now fails the build. The same guard closed a second bug found while testing it, where running the prerender twice without an intervening build matched nothing, no-oped, and wrote the homepage into every route while printing a success line for each.

  5. seo.config.json became the single source of truth for per-route head tags, read both by the prerender script and by the page components, so the HTML a crawler receives and the HTML a browser renders cannot drift apart again.

  6. A five-dimension audit of the repository returned 82 claim findings: 36 fabricated, 36 unverifiable, 10 checkable. Among them, eight invented client testimonials stamped 'Verified Purchase' and dated with frozen relative strings, an AggregateRating of five from fifty reviews injected as JSON-LD, partner badges for two companies whose programmes the practice had never joined, and guarantees on a site nobody was monitoring.

  7. The cut followed from the audit rather than the other way round: fixing the canonical bug alone would have made thirty pages of unmaintained generated copy individually indexable, which is worse than the bug. Twenty-seven URLs were retired behind 301 redirects, two of them to the fuller write-ups on this portfolio, and three pages remain.

  8. What replaced the invented proof already existed: ten real certifications, nine linking to the issuer's own verification page and the tenth stating plainly that none was issued, and the public Fiverr aggregate linked to the profile that publishes it. The reviews stay with their authors rather than being reproduced.

  9. vercel.json handles the host redirect, cleanUrls, an SPA rewrite that excludes asset and SEO paths, year-long immutable caching for hashed assets, and seven security headers including a scoped Content-Security-Policy.

Architecture

elitepick-ai · flow
seo.config.json (per-route head, one source of truth)React Router SPA (3 page components, shadcn/Radix UI)vite buildscripts/prerender.mjs rewrites BOTH head and body per route, failing the build if any pattern stops matching exactly onceVercel static edge (CSP/HSTS headers, cleanUrls, host redirect, 301s for 27 retired URLs)visitor. Lead capture posts from the contact form to the Web3Forms API.
ComponentRole
seo.config.jsonPer-route title, description and og:type. Read by scripts/prerender.mjs under Node and imported by the page components under Vite, so the prerendered head and the client-rendered head cannot diverge.
scripts/prerender.mjsPost-build step writing dist/<route>/index.html with that route's head, JSON-LD graph and semantic body. replaceOnce() throws unless a pattern matches exactly once, so a silent no-op becomes a failed build.
src/site.tsEvery fact the site states about its owner, each one checkable: ten certifications with the issuer's verification URLs, and the public Fiverr aggregate linked to its source.
SEOHelmet componentClient-side head for the three routes, reading the same seo.config.json the prerender uses.
vercel.jsonEdge configuration: host redirect, cleanUrls, SPA rewrites excluding asset and SEO paths, 301s for the 27 retired URLs, immutable caching, and seven security headers.
Web3Forms integrationServerless lead capture from the contact form, with a documented public access key and an environment override.

Trade-offs

  • Chose

    Post-build static HTML injection into the compiled Vite bundle

    Over

    Server-side rendering or migrating to a framework such as Next.js

    The site ships as static files with no server runtime. Rewriting dist/<route>/index.html after the build buys crawler-readable content without a server, at the cost of maintaining a second, hand-authored copy of each page's markup inside the script. That cost is exactly what produced the head bug: two copies, one of them never verified.

  • Chose

    Cutting the site to three pages

    Over

    Fixing the canonical bug and keeping thirty

    The bug was suppressing the content rather than the other way round. Repairing the head without touching the copy would have promoted twenty-seven pages of unmaintained generated marketing text, and several invented claims, from invisible to individually indexable. The smaller site is the honest one and takes no upkeep.

  • Chose

    A build that fails when a substitution pattern stops matching

    Over

    Substitutions that silently no-op, as before

    The original defect was undetectable by inspection: the script ran, printed a success line per route, and produced files that looked right unless you diffed their heads. Making the failure loud converts a silent six-month regression into a broken build.

  • Chose

    Linking proof that lives elsewhere

    Over

    Reproducing testimonials and credentials on the site

    A review copied onto your own site is a claim; a review on the platform that published it is evidence. The same holds for certifications, where the issuer's verification URL is worth more than a badge. It also removes any temptation to write the flattering version.

  • Chose

    A typed content layer committed alongside the code

    Over

    A headless CMS or database

    Content is versioned, type-checked and diffable with the rest of the application, and one definition feeds the router, the sitemap and the pre-rendered HTML.

At scale

  • Build time fell from 42s to 4.6s, mostly by deleting 17 blog images of 1.7 to 2.9 MB each

  • Three route components remain over 49 shadcn/Radix UI primitives, down from thirteen

  • The typed content layer covered 9 projects, 8 services, 2 tool pages and 3 long-form posts; it is now one file of verifiable facts about one person

  • A Google Cloud service-account private key sat in this public repository from 2026-02-20 until it was found and revoked in September 2026

  • The indexing client it belonged to submitted to an API that accepts only JobPosting and BroadcastEvent pages, so every submission it made was accepted and ignored

  • Test coverage is a single trivial assertion; playwright.config.ts is present with no committed specs

My role

Sole author and operator, of both the original build and the reduction. The interesting half of the work was the second one: reading the site's own Search Console data, finding that a single unchecked string substitution had been consolidating every URL into the homepage for six months, and then deciding that the right response was to delete most of the site rather than to repair it.