Skip to content
ExponaBLOG
TECHNICAL SEO FOR DEVELOPERS · DAY 096 / 100

Technical SEO mistakes developers make, explained simply

The issues I find most often in audits.

Expona Admin6 min read

I've audited hundreds of production sites over five years, and I keep finding the same preventable issues. These technical SEO mistakes developers make cost companies thousands in organic traffic, yet they're trivial to fix once you know what to look for. Today I'm sharing the five issues I encounter most often—and how to avoid them.

1. Staging indexed — Public staging sites with no protection or noindex

This is the mistake I see at least once a month. Your staging environment is live at staging.yourdomain.com or yourdomain-staging.herokuapp.com, accessible to anyone, and search engines are indexing it. You end up with duplicate content across production and staging, diluting your organic visibility and confusing Google about which version is canonical.

I worked on an e-commerce platform where staging was indexed for six months before we caught it. The staging site ranked for brand keywords instead of production, costing us conversions because the staging environment had outdated product information and broken payment flows.

The fix is straightforward: block staging at the server level or add a noindex tag. Choose one approach based on your setup:

  • Robots.txt approach: Add User-agent: * and Disallow: / for all crawlers
  • HTTP Basic Auth: Require credentials to access staging
  • noindex meta tag: Add <meta name="robots" content="noindex, nofollow"> to your base layout
  • X-Robots-Tag header: Return X-Robots-Tag: noindex, nofollow from your staging server

I recommend combining HTTP Basic Auth with noindex for defense-in-depth. If someone bypasses auth, noindex still protects you.

html
<!-- In your staging environment's base layout -->
<meta name="robots" content="noindex, nofollow">
<meta name="googlebot" content="noindex, nofollow">

2. noindex shipped to production — An environment flag left on at launch

You test your site locally with noindex enabled to avoid polluting Google's index. Then at launch, you forget to disable the flag. I've seen production sites sit invisible for weeks because someone left NOINDEX_ENABLED=true in the config.

This happened to a travel platform I worked with. They launched a new site redesign, and the entire site was noindex'd because the flag wasn't toggled off in production. It took us three days to track down—three days of zero organic traffic.

Prevent this with environment-specific configuration:

html
<!-- React example -->
<meta name="robots" content={process.env.REACT_APP_ENV === 'production' ? 'index, follow' : 'noindex, nofollow'}>

Better: use a deployment checklist that explicitly verifies noindex is off in production. Automate this check in your CI/CD pipeline by parsing the HTML and asserting noindex is absent on your production domain.

Use environment-based toggles for all crawl directives. Never hardcode noindex in your templates.

You build a React or Next.js app and render all content client-side. Google's crawlers execute JavaScript now, but it's slow and unreliable. Content that loads after a second or two often gets missed, and internal links hidden behind JavaScript don't get followed properly.

I audited a SaaS platform where product descriptions, pricing, and navigation links only appeared after React hydrated. Google indexed the page but missed 60% of the internal links and all the dynamic pricing tiers. Organic traffic was capped because crawlers never saw the real structure.

The solution depends on your framework:

  • Next.js: Use SSR or Static Generation. Render critical content server-side, hydrate with client-side interactivity.
  • NestJS + React: Server-render your React app on initial load, send full HTML to crawlers.
  • Pure SPA: If you must use a SPA, pre-render critical pages at build time or use dynamic rendering (serve fully-rendered HTML to crawlers only).
html
<!-- Next.js: fetch data server-side, render on the server -->
export async function getServerSideProps(context) {
  const products = await fetch('/api/products');
  return { props: { products } };
}

export default function Products({ products }) {
  return (
    <>
      {products.map(p => (
        <div key={p.id}>
          <h3>{p.name}</h3>
          <p>{p.description}</p>
          <a href={`/products/${p.id}`}>View</a>
        </div>
      ))}
    </>
  );
}

This ensures crawlers see all content and links in the initial HTML response, not after JavaScript runs.

4. Broken canonicals — Canonicals pointing to staging, http or the homepage

A canonical tag tells Google which version of a page is the primary one. A broken canonical—pointing to staging, an HTTP URL, or the homepage—confuses crawlers and dilutes your link equity.

I found a trading platform with canonicals pointing to http://yourdomain.com/page while the actual page was at https://yourdomain.com/page. Google preferred the HTTP version, which didn't exist. Another client had canonicals hardcoded to the homepage for every product page.

Common issues:

  • Canonical pointing to staging domain instead of production
  • Canonical using HTTP instead of HTTPS
  • Canonical missing the page path (defaulting to homepage)
  • Relative canonicals that break in certain contexts

Always build canonicals dynamically based on the request URL:

html
<!-- NestJS + Handlebars example -->
<link rel="canonical" href="https://yourdomain.com{{requestPath}}">

For Next.js, use the next/head component or built-in SEO plugins that handle canonicals automatically. Never hardcode a canonical to a staging domain—use environment variables.

html
<link rel="canonical" href={`${process.env.NEXT_PUBLIC_CANONICAL_URL}${router.asPath}`}>

5. Soft 404s — Error pages that return 200

A soft 404 is a page that doesn't exist but returns an HTTP 200 status code instead of 404. Google crawls it, wastes crawl budget, and treats it as valid content. I once found a CRM where deleted records still returned a "not found" message with a 200 status—Google indexed hundreds of ghost pages.

Soft 404s usually happen when:

  • You render a "not found" message but forget to set the status code
  • Your routing catches all paths and returns 200 for everything
  • You use middleware that always returns 200, even for missing resources

The fix is simple: return the correct HTTP status code. In Next.js, use notFound(). In NestJS, throw a NotFoundException. In Express, use res.status(404).send().

html
<!-- Next.js app router -->
export default function ProductPage({ params }) {
  const product = getProduct(params.id);
  
  if (!product) {
    notFound(); // Returns 404 status
  }
  
  return <div>{product.name}</div>;
}
html
<!-- NestJS -->
@Get(':id')
getProduct(@Param('id') id: string) {
  const product = this.service.findById(id);
  if (!product) {
    throw new NotFoundException('Product not found');
  }
  return product;
}

Google Search Console will flag soft 404s in the Coverage report. Check it regularly and fix any pages returning 404 status that should return 200, or vice versa.

Common mistakes

  • Testing with noindex enabled, forgetting to disable for production: Use environment variables and automated checks in your CI/CD pipeline.
  • Assuming Google handles client-side rendering well: It doesn't, not reliably. Always server-render critical content and links.
  • Hardcoding URLs in canonicals or meta tags: Build them dynamically from the request or use environment-based configuration.
  • Letting staging get indexed: Add noindex at the server level. Don't rely on robots.txt alone.
  • Returning 200 for everything: Return proper HTTP status codes. Use your framework's built-in error handling.

Key takeaways

  • Block staging with HTTP Basic Auth + noindex. Never let staging get indexed.
  • Use environment-based configuration flags for all crawl directives. Automate verification in CI/CD.
  • Server-render critical content and links. Don't rely on client-side rendering for SEO-important content.
  • Build canonicals dynamically from the request URL. Never hardcode them, especially not to staging or HTTP.
  • Return correct HTTP status codes. 404 for missing pages, 200 for valid ones. Verify in Google Search Console.
  • Add these checks to your deployment checklist: noindex off, canonicals correct, staging blocked, status codes verified.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles