← All insights
Technical SEO/Home services/9 min read

A Website Redesign Lost 64% of Its Traffic. Here Is How I Found Out Why.

An electrical contractor in Dallas-Fort Worth replatformed to a JavaScript front end and lost most of its organic leads in seven weeks. The pages looked fine in a browser. Here is how I found what Google was actually seeing, what was missing, and what it took to get the leads back.

Edward Humphries
Edward Humphries

A website redesign is supposed to be the good news. This one cut organic traffic by 64 percent in seven weeks, and quote requests went from about 85 a week to fewer than 25.

The company is a commercial and residential electrical contractor in the Dallas-Fort Worth area, 28 service vans, two divisions, and a business that lives on searches like “commercial electrician Dallas” and “24/7 electrical repair Fort Worth.” In mid-2025 it moved from WordPress to a headless build: a Next.js front end on a decoupled CMS, to support online booking, financing calculators and live dispatch tracking. Good reasons. The site looked great in a browser.

I was brought in as the technical SEO lead to find out why the rankings vanished and to get them back. The whole diagnosis came down to one question: what does Google get when it asks for the page, and how different is that from what a person sees? The rest of this article is how I answered it with a crawler, what it turned up, and what the fixes did.

The redesign went live and the leads went with it

The agency that built the site assumed modern search bots render JavaScript the way Chrome does, so putting the page content behind client-side hydration was fine. Sometimes it is. Here it was not, and there was no measurement in place to notice. Within seven weeks the transactional queries that fed the phones had dropped off page one entirely, and the internal team was arguing about whether it was the redesign, the URL changes, a Google update, or bad luck.

That argument is the reason I start with a crawl rather than opinions. A crawler can fetch a page the way a bot does, then render it the way a browser does, and show you the difference line by line. Nobody argues with a diff.

1Rendering

What the raw HTML showed versus what the rendered page showed

How I looked: Screaming Frog in JavaScript rendering mode with an 8-second render window, run against the same URL list as a plain HTML crawl. Then compare word count, headings, internal links and structured data between the two.

The gap was not subtle. On the commercial service pages, 78 percent of the body copy existed only in the rendered DOM. In the raw HTML there was a title tag, an empty root div and a script bundle. Headings, service descriptions, reviews, the lot, all arrived after JavaScript ran.

Worse, the render was not reliable. Two script bundles threw uncaught exceptions on some templates, and the rendering log showed pages that never finished inside Google’s patience. A bot that gives up at five seconds got the empty version. That matched the pattern in Search Console: pages indexed, but indexed as near-empty documents that ranked for nothing.

The internal links were the second casualty. The suburb pages for Frisco, McKinney, Arlington, Irving and the rest were linked only from a JavaScript menu that failed to attach during a bot pass. So even where a page was fine, the paths to it were not.

I did not ask the developers to take my word for it. I gave them the stack traces from the render log and the side-by-side extracts for a dozen templates. That turned a week of debate into a two-line decision: move the core content and the navigation to server-side rendering and static generation, and keep the interactive tools client-side where they belong.

What I comparedRaw HTMLRendered DOMWhat it meant
Body copy on commercial service pagesAbsent on 78% of pagesPresentPages indexed as empty documents
Links to suburb service pagesNot presentPresent in the menuLocation pages effectively orphaned
Structured data (JSON-LD)NoneInjected after loadNo local pack or rich result eligibility
Render completionn/aTimeouts and 2 uncaught errors on key templatesSome bot visits never saw the content at all
2Migration

The URLs that never got mapped, and a staging site left open

How I looked: a crawl of the old sitemap and the old URL list from Search Console, checking each one’s status code, redirect target and canonical on the new site. Then a crawl of the staging host to see whether it was reachable and indexable.

About 180 location and commercial sub-service URLs had changed structure with no redirects. The old addresses returned soft 404s, which is the worst of both worlds: a 200 status with nothing on it, so Google keeps the old page in the index and slowly learns it is worthless. The new pages were reachable only through the same dynamic menu that bots could not use.

The staging environment, where the booking flow was being tested, had also been left crawlable without canonical tags pointing at production. Duplicate copies of core pages, on a host nobody wanted indexed, competing with the real ones.

Neither of these is a JavaScript problem. They are launch checklist problems, and they were compounding the rendering problem. A redirect map, canonicals on production, and a login gate on staging fixed them in the first sprint.

3Content

Custom extraction found 412 pages still showing placeholder text

How I looked: custom extraction across roughly 2,400 programmatic location and service pages, pulling specific elements out of the rendered DOM with XPath, CSS selectors and regex, then sorting the results for anything that did not look like a real value.

The site generated pages for every service in every city it served. That is fine when the templates work. I extracted the pieces that had to be real on every page: the local dispatch response time (//div[@data-testid='local-dispatch']/span/text()), the license number in the sub-footer (span.license-verification), and the local tracking phone number by pattern.

The extraction came back with 412 suburb pages where the injection script had failed and the page was live with tokens still in it: “[City] Emergency Services Available” on a page that was supposed to say Grapevine. That is not a ranking problem, it is a trust problem, and no human review of 2,400 pages would have found all of them. A crawl found them in an afternoon and the templates were patched that week.

4Structured data

Schema that Googlebot never saw

How I looked: structured data validation in the crawl, against both Schema.org and Google’s rich result requirements, on the raw fetch and the rendered fetch separately.

The old WordPress site had ugly hardcoded microdata that at least declared a LocalBusiness. The new site injected JSON-LD through a tag manager container after page load, so the raw fetch had no structured data at all. Where the rendered version did have it, validation flagged conflicting @id values across nested LocalBusiness entities, a deprecated property in the Electrician markup, and FAQ schema on mobile that shipped empty answer objects.

We rebuilt the JSON-LD as part of the server-rendered template: Electrician as the business type, service areas as geo shapes for the counties served, opening hours, license IDs, and FAQ and breadcrumb markup that matched what was visibly on the page. Validation went to a 100 percent pass rate across all 2,400 URLs, and the review stars and FAQ accordions came back in the results.

5Prioritization

Blending Search Console and GA4 into the crawl to find the expensive mistakes first

How I looked: Search Console, GA4 and a backlink index connected to the crawl through their APIs, so every URL row carried 90 days of clicks and impressions, its conversion rate, and its referring domains alongside its technical status.

A crawl of 2,400 pages produces thousands of issues. The question is never “what is wrong,” it is “what is wrong on the pages that make money.” Joining performance data to the crawl answers that in one sort.

The best example: 43 commercial service pages with real external links from chamber of commerce directories and trade publications, and zero impressions for 60 days. The reason was a noindex directive set in a client-side header component that only some templates loaded. Nobody had put it there on purpose. Those 43 pages were the first fix after rendering, because they had authority waiting to be used.

6Architecture

Flattening the site from seven clicks deep to three

How I looked: the crawl’s force-directed link graph, which draws every page as a node pulled toward whatever links to it. Isolated clusters and dead weight show up as shapes before you read a single URL.

The graph made the structural problem visible to people who do not read crawl exports. High-margin commercial services such as switchgear and EV infrastructure floated as satellite clusters six or seven clicks from the homepage. Blog posts from 2018 sat one click off the main navigation. Project gallery filters generated thousands of near-identical URLs that soaked up crawl budget and internal link equity.

Before Commercial page, 7 clicks

1Home
2Services (JS menu)
3All commercial services
4Category listing, page 2
5Gallery filter: commercial
6Project detail
7Switchgear maintenance
Reachable, in theory, through a menu bots could not open and a gallery filter loop.

After Same page, 3 clicks

1Home
2Commercial electrical services hub
3Switchgear maintenance
·Cross-linked: panel upgrades, EV infrastructure
·County hub → city page → this service in that city
Server-rendered links, breadcrumbs, and gallery filters set to noindex with canonicals.
Three hubs, then everything under them. Commercial services, residential services, and four county hubs with a page per city. Maximum click depth for any commercial landing page went from seven to three.
7Not breaking it again

Staging crawls before every release, and a weekly crawl after

How I looked: forms-based authentication in the crawler to get through the staging login, crawl comparison mode to diff staging against production before each release, and a scheduled headless crawl from the command line every week after launch.

The original launch failed because nobody crawled the new site before it replaced the old one. So every sprint after that, staging got crawled behind its login and compared against production: canonicals, indexability, redirects, heading structure, word counts and link counts, side by side. If a release was going to remove content from the raw HTML or introduce a redirect loop, we knew on a Thursday, not from Search Console three weeks later.

After the recovery, a small script runs the crawler headless every Sunday night, pulls the week’s Search Console and GA4 numbers into it, and drops a report of 4xx and 5xx responses, missing titles, schema failures, canonical mismatches and new orphan pages where the team sees it Monday morning. The point is not the report. The point is that the next regression gets caught before Google indexes it.

Two things I would rather not have had to do by hand, and did not: writing meta descriptions for hundreds of location pages that had none, which ran through an LLM call inside the crawl with the city, service and body extract as inputs, then got a human read before publishing; and expanding every FAQ accordion and tab to check what was inside, which a short browser script inside the render step handled across every template.

What changed over seven months

The rendering fix and the redirect map went out first and produced the fastest recovery. The architecture work and the schema rebuild followed over the next two sprints. Numbers below compare the five months after the rendering fix to the seven weeks of collapse before it.

+218%organic sessions, five months after the rendering fix
14 → 186page-one keywords for Dallas‑Fort Worth electrical searches
32% → 98%of Googlebot requests landing on valid, canonical 200 pages
25 → 142qualified organic leads a week, forms and calls

“Commercial electrician Dallas” went from outside the top 100 to position 2. “24-hour emergency electrician Fort Worth” from page six to position 1. Residential emergency calls up 165 percent. The commercial division signed three multi-year design-build contracts traced to organic landing page inquiries, about $1.4 million in closed pipeline.

What I would not claim from these numbers

The fixes overlapped. Rendering, redirects, architecture and schema went out within a few sprints of each other, so I cannot tell you what share of the recovery each one earned. The rendering fix came first and the curve turned first, which is the strongest evidence I have, and it is still correlation.

The 142 leads a week is also well above the 85 the old site produced before the redesign, and some of that is the new site doing what it was built to do: 2,400 pages that did not exist before, online booking, and a much better mobile experience once the content was actually there. The redesign was a good idea. The launch was the problem.

The method, for any site that renders with JavaScript

  1. Crawl it twice. Once as plain HTML, once with JavaScript rendering. Diff word count, headings, links and structured data. The gap is your problem list.
  2. Read the render log, not just the render. Timeouts and uncaught errors tell you which templates bots see empty some of the time.
  3. Give developers stack traces, not opinions. The decision to server-render took an afternoon once they could see the failure.
  4. Extract what has to be true on every page. Phone numbers, license IDs, city names. Sort for anything that is not a real value.
  5. Validate schema on the raw fetch. If it only exists after load, assume the bot never saw it.
  6. Join performance data to the crawl. Fix the pages with authority and no impressions before the pages with neither.
  7. Draw the link graph. Click depth and orphaned clusters are visible in seconds and persuasive to people who will never open a spreadsheet.
  8. Crawl staging before every release, and production every week. Behind the login, compared against live. This is the step that would have prevented the whole thing.
Read this next
Why Website Projects Go Over Budget: 10 Hidden Causes Most Teams Miss

The traffic loss in this case study is one of ten ways a website project quietly goes over budget. The old site is the largest unscoped deliverable, and that is only number five.

And then this
13 Hidden Ways Claude Can Help Website Project Managers

The crawl-to-Search-Console join that found this problem, and the weekly crawl diff that would have caught it, are two of thirteen things I now hand to Claude.

Five checks before you sign off on a redesign launch

You do not need to run the crawler yourself. You need to ask for these before the new site replaces the old one.

  1. Ask for the raw HTML of your three most valuable pages. If the body text is not in it, ask how search engines will get it.
  2. Ask for the redirect map. Every old URL that had traffic should have a destination, and someone should have tested it.
  3. Ask whether staging is behind a login and whether production pages carry canonical tags. Both answers should be yes.
  4. Ask what will be measured in the first two weeks after launch, and who is watching it. Impressions and indexed pages in Search Console, at minimum.
  5. Ask for the crawl report of the staging site, compared against the live site. If nobody has run one, the launch is not ready.

A redesign that looks right in a browser and wrong to a crawler will cost you months. The crawl before launch costs a day.

Read more SEO & Search Visibility articles →
Newsletter

Get Insights in Your Inbox

Real lessons from managing websites that have to make money: conversion, SEO, UX, and the project decisions behind them. No spam, and you can unsubscribe anytime.

You'll get a confirmation email first. Click the link in it and you're in.

Currently Available

Looking for a Website Project Manager Who Can Own It All?

Whether you’re building something new or keeping a site running, I’m currently taking on new work. Tell me about it, and I’ll tell you exactly how I’d manage it.