DEV Community

Cover image for 12 SEO Mistakes Developers Ship to Production (and How to Catch Them)
Andrei SEO
Andrei SEO

Posted on AI-assisted

12 SEO Mistakes Developers Ship to Production (and How to Catch Them)

Most SEO problems I find in audits aren't content or keyword problems. They're bugs. The site looks perfect in the browser, Lighthouse is green, the client is happy, and Google quietly refuses to index half of it.

The tricky part is that these bugs don't throw errors. Nothing breaks for the user. So here are the 12 I see most often, with a quick way to catch each one.

1. Shipping the staging noindex to production

The classic. Staging was set to "discourage search engines", the site went live, and nobody unchecked it.

In WordPress, check it from the CLI:

wp option get blog_public
# 0 = search engines discouraged, 1 = indexable
Enter fullscreen mode Exit fullscreen mode

For any stack, check both the HTML and the response headers, since X-Robots-Tag is easy to forget in a server config:

curl -s https://example.com/ | grep -i '<meta name="robots"'
curl -sI https://example.com/ | grep -i 'x-robots-tag'
Enter fullscreen mode Exit fullscreen mode

Add this to your launch checklist. Better yet, make it a deploy check.

2. robots.txt blocking what Google needs to render the page

Blocking /wp-content/ or /assets/ "for security" means Googlebot can't load your CSS and JS, so it can't render the page the way users see it.

# Don't do this
User-agent: *
Disallow: /wp-content/
Enter fullscreen mode Exit fullscreen mode

The reverse is just as common: low-value URLs (internal search, filter combinations, admin-ish paths) left fully crawlable. robots.txt controls crawling, not indexing, so for pages that must stay out of the index, use noindex and let Google crawl them.

3. Content that only exists after JavaScript runs

With client-side rendering, the initial HTML can be an empty shell. Google does render JavaScript, but it's slower, less reliable, and any error means content (or links) simply don't exist for Googlebot.

Quick test: compare the raw HTML with what you see in the browser.

curl -s https://example.com/services/ | grep -c 'Your main heading text'
# 0 means the content isn't in the initial HTML
Enter fullscreen mode Exit fullscreen mode

Then check the rendered HTML in Search Console's URL Inspection tool. If your main content, menu, or product links only appear after hydration, consider SSR or static generation for those routes.

4. Infinite scroll and "load more" without real URLs

Googlebot doesn't scroll or click buttons like a user. Content loaded on scroll events or behind a "Load more" button may never be seen.

Keep the nice UX, but back it with crawlable pagination:

<!-- Crawlable fallback the JS can progressively enhance -->
<a href="/blog/page/2/">Next page</a>
Enter fullscreen mode Exit fullscreen mode

Each page of results should have its own URL that works without JavaScript.

5. Canonical and noindex misconfigurations

A few variations I keep finding:

  • Every page canonicalized to the homepage (a template bug)
  • Canonical pointing to a URL that redirects or returns 404
  • noindex and canonical to another URL on the same page (conflicting signals)
  • Canonical tags added only by JavaScript

Check what's actually in the HTML:

curl -s https://example.com/some-page/ | grep -i 'rel="canonical"'
Enter fullscreen mode Exit fullscreen mode

Then crawl the site (Screaming Frog or similar) and filter for canonicals that don't point to themselves. Every non-self canonical should be intentional.

6. The CMS generating thousands of empty pages

In a recent audit, a local contractor's site had about 2,600 URLs. Only 58 were service pages. Around 1,650 were image attachment pages and about 700 were tag archives, most with a single post.

WordPress creates an attachment page for every upload. Since WordPress 6.4, new installs have them disabled, but older sites keep them on:

wp option get wp_attachment_pages_enabled
wp option update wp_attachment_pages_enabled 0
Enter fullscreen mode Exit fullscreen mode

For tag archives, if your SEO plugin doesn't handle it, the core wp_robots filter does:

add_filter( 'wp_robots', function ( $robots ) {
    if ( is_tag() ) {
        $robots['noindex'] = true;
        $robots['follow']  = true;
    }
    return $robots;
} );
Enter fullscreen mode Exit fullscreen mode

Also check your sitemap index. If you see attachment-sitemap.xml or a huge post_tag-sitemap.xml, you have this problem.

7. Building URLs with string concatenation

This one I found on my own site yesterday. In the page source:

<a href="https://example.comwp-content/uploads/2024/12/image.webp">
Enter fullscreen mode Exit fullscreen mode

The missing slash came from an old search-replace (https://old-domain/ → https://example.com). Users never noticed, because the Elementor lightbox opened the image from a separate, correctly generated URL. Crawlers, on the other hand, saw a broken link on every affected page.

In your own code, let the framework join paths:

// Fragile
$url = home_url() . 'wp-content/uploads/image.webp';

// Safe
$url = home_url( '/wp-content/uploads/image.webp' );
$url = trailingslashit( home_url() ) . 'wp-content/uploads/image.webp';
Enter fullscreen mode Exit fullscreen mode

And when you need a search-replace, always do a dry run first:

wp search-replace 'https://old-domain.com' 'https://example.com' --dry-run
Enter fullscreen mode Exit fullscreen mode

Note that page builders often store data as JSON with escaped slashes (https:\/\/example.com), so a plain string replace can miss some occurrences and catch others.

8. Migrations without redirects

New site, new URL structure, and the old URLs return 404. Every backlink pointing to them is now wasted, and Google has to figure out the new site from scratch.

Before launch:

  1. Export all old URLs (crawl + sitemap + Search Console pages report)
  2. Export URLs with backlinks (Ahrefs, Semrush, or Search Console's links report)
  3. Map each one to its closest new equivalent with a 301
  4. After launch, crawl the old URL list and confirm each one returns a single 301 to a 200, with no chains

9. Hreflang errors on multilingual sites

The most common ones:

  • Missing return links (page A references B, but B doesn't reference A)
  • Wrong codes (en-UK instead of en-GB)
  • Hreflang injected by JavaScript
  • Pointing to non-canonical or redirected URLs

Every language version must list all versions, including itself:

<link rel="alternate" hreflang="en" href="https://example.com/en/page/" />
<link rel="alternate" hreflang="ro" href="https://example.com/ro/pagina/" />
<link rel="alternate" hreflang="x-default" href="https://example.com/en/page/" />
Enter fullscreen mode Exit fullscreen mode

10. Links Google can't follow

Google follows <a href> links. It doesn't reliably follow:

<!-- Not a link for Google -->
<span onclick="location.href='/pricing/'">Pricing</span>
<button onclick="router.push('/pricing/')">Pricing</button>

<!-- A link -->
<a href="/pricing/">Pricing</a>
Enter fullscreen mode Exit fullscreen mode

Combined with a page that's only reachable from the sitemap, you get orphan pages. Compare the URLs in your sitemap with the URLs found by a crawl starting from the homepage. Anything in the sitemap but not in the crawl has no internal links, and Google will treat it as unimportant.

11. Optimizing the wrong performance metric

I'll probably get some disagreement here, but: the PageSpeed Insights score is a lab score, not a ranking factor. I regularly see pages with a score under 10 ranking first in competitive local searches, and pages scoring 97 losing to pages scoring 60.

Core Web Vitals do matter, but Google uses field data from real users (the CrUX section at the top of the report), not the lab score. If your field data passes, spending a week moving the lab score from 70 to 95 rarely moves rankings. That week is usually better spent on any of the other 11 items in this list.

12. Not checking a domain's history

A client bought a domain that looked brand new and perfect for their market. Three months and 30+ pages later, almost nothing was indexed. The domain had been used as a redirect around 2004 and was still distrusted by Google almost 20 years later.

Check before buying. The Wayback Machine's CDX API shows the earliest captures:

https://web.archive.org/cdx/search/cdx?url=example.com&limit=10
Enter fullscreen mode Exit fullscreen mode

If there's history, open a few snapshots and look for spam, redirects, or unrelated content.

A launch checklist you can steal

  • [ ] noindex removed (HTML meta and X-Robots-Tag header)
  • [ ] robots.txt doesn't block CSS, JS or important sections
  • [ ] Main content and links present in the raw HTML
  • [ ] Pagination has real <a href> URLs
  • [ ] Canonicals self-reference unless intentional
  • [ ] No attachment, empty tag or test pages in the sitemap
  • [ ] No malformed URLs in a full crawl
  • [ ] Old URLs 301 to new ones, no chains
  • [ ] Hreflang reciprocal (if multilingual)
  • [ ] Sitemap URLs all found in a crawl from the homepage
  • [ ] Domain history checked

None of these require an SEO specialist, just a few minutes of checking before the client finds out from their traffic.


I wrote a broader version of this for business owners, covering the strategy side too (audits, keyword research, content quality): 12 Common SEO Mistakes (And How to Avoid Them).

What's the strangest SEO bug you've found in production?

Top comments (0)