slop audit
Blog post 44 / Crawlability

Noindex tag: find and fix accidental indexing blocks

Find a noindex tag in HTML or HTTP headers, fix accidental production blocks, and test indexing directives in CI before launching your website.

By Slop Audit · 9 min read

A noindex tag tells supporting search engines to exclude a page from their index. If a production page should appear in search, inspect its delivered HTML and HTTP headers, remove the unintended directive at its source, and verify the deployed response again. Adding an index tag or submitting a sitemap will not cancel a remaining exclusion.

For a portfolio, student project, documentation site, or product launch, this is a release check you can run before discovering missing pages in Search Console. Keep intentional exclusions intact: the goal is to publish the right pages, not make every route indexable.

Noindex tag troubleshooting checklist

  1. Decide whether the exact URL should be eligible for search.
  2. Request it without a logged-in session and record its final URL.
  3. Inspect every applicable robots meta tag in the response HTML.
  4. Inspect all X-Robots-Tag response headers.
  5. Compare the delivered directives with the rendered document.
  6. Find the template, CMS setting, middleware, or hosting rule that emits the block.
  7. Deploy the scoped fix and check the public response again.
  8. Confirm crawl access and canonical URL separately.
  9. Add route-specific assertions to CI.
  10. Use Search Console to distinguish the live fix from Google's last crawl.

Save the URL, status, directives, deployment version, and expected policy with the result. A screenshot of the page cannot show a hidden response header.

What a noindex tag does

For HTML, the usual declaration belongs in the document head:

<meta name="robots" content="noindex">

A crawler-specific declaration can also matter:

<meta name="googlebot" content="noindex">

Google's noindex implementation guidance covers both meta tags and HTTP headers. Google must fetch the resource to discover the directive; putting noindex in robots.txt is unsupported.

Use each control for its own job. noindex controls search inclusion, robots.txt controls cooperating crawlers' access, and authentication controls who can view private material. A publicly accessible page with noindex is still publicly accessible. nofollow concerns links; it is not a substitute for excluding the page itself.

MDN's robots meta reference describes these distinct directives. For crawl-rule testing, follow the separate robots.txt testing guide.

Decide whether the exclusion is intentional

Write a small route inventory before editing a shared layout:

/                       public landing page    eligible for search
/docs/getting-started    public documentation   eligible for search
/blog/release-guide     published article      eligible for search
/preview/draft          public preview         intentionally noindex
/account                personal account       access-controlled

These are example policies, not a rule for every site. A public preview may be acceptable for an open-source draft; a confidential preview needs access control. Search results, temporary campaign pages, and generated duplicates need an explicit publishing decision from the team.

If Search Console reports “Excluded by ‘noindex’ tag” for a URL you intentionally exclude, preserve the policy. If the affected URL is a launch-critical public page, continue the investigation. Check a representative page from every template: one homepage fix may leave all documentation pages excluded.

Find the noindex tag in the deployed response

Use a GET request so you inspect the same response body a public visitor receives. Replace the example URL:

curl --silent --show-error --location \
  --dump-header /tmp/indexing-headers.txt \
  --output /tmp/indexing-page.html \
  --write-out 'Final URL: %{url_effective}\nStatus: %{http_code}\n' \
  https://example.com/docs/getting-started

rg -n -i 'x-robots-tag|^HTTP/' /tmp/indexing-headers.txt
rg -n -i 'robots|noindex|nofollow' /tmp/indexing-page.html

The header dump includes redirect responses as well as the final response. Identify which block belongs to the final page before assigning a cause. A redirect to a login page or another hostname is already a reason to investigate the route.

The text search is a shortlist. A blog article may mention noindex in prose or a code example without excluding itself. Inspect actual <meta> elements, including multiline tags and crawler-specific names, rather than treating every matching word as a directive. Check attribute values and document context in browser developer tools.

Compare the result with the title and meta description checklist. A page can have polished search metadata while a separate robots directive prevents its inclusion.

Check X-Robots-Tag when the HTML looks correct

An exclusion can come from the server or CDN:

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
X-Robots-Tag: noindex

The header can also control non-HTML resources such as PDF downloads. MDN's X-Robots-Tag reference explains header directives and crawler scoping. Check repeated headers and values such as googlebot: noindex, not only a single plain noindex line.

Google's robots directive specification says conflicting applicable rules resolve to the more restrictive rule. An index meta tag does not override an applicable noindex header. The value none also excludes indexing because it means noindex, nofollow.

Trace the emitting layer. A route template may be clean while a hosting rule adds the header to every response. Inspect middleware, reverse-proxy configuration, CDN response transformations, and CMS publishing settings. Fix the rule that owns the policy rather than adding a competing tag elsewhere.

Compare initial HTML with the rendered page

In the browser console, inspect rendered robots tags:

Array.from(document.querySelectorAll("meta[name]"))
  .filter((tag) => /^(robots|googlebot)$/i.test(tag.name))
  .map((tag) => ({ name: tag.name, content: tag.content }));

Compare these values with the saved response. JavaScript can add tags, leave duplicates during route changes, or display a different policy after hydration.

Do not ship an initial noindex and rely on JavaScript to remove it for a public page. Google's JavaScript SEO guidance says it may skip rendering when the initial response contains noindex. Emit the intended indexing policy in the initial response, then keep client-side navigation consistent with it.

For example, test navigation from a noindexed preview to a published article. A shared head component must not leave the preview's tag behind. Also load the article directly, because successful client navigation does not prove the server returns the same metadata.

Remove accidental blocks without exposing drafts

A common deployment mistake is copying a preview policy into production. Find out how your application identifies a deployment before changing it. A framework's optimized build mode alone may not distinguish a public production deployment from a preview build.

Define an explicit publishing environment and combine it with route-level visibility:

type Visibility = "published" | "preview";
type SiteEnvironment = "production" | "preview";

function robotsPolicy(
  environment: SiteEnvironment,
  visibility: Visibility,
): string {
  return environment === "production" && visibility === "published"
    ? "index, follow"
    : "noindex, follow";
}

Validate the environment configuration during deployment. Use the resulting policy in the server-rendered head or response layer your stack owns. This function illustrates a policy decision; it does not configure your framework or remove an existing CDN header.

After deploying, invalidate affected cached responses when necessary and repeat the public GET check. Test a published page and an intentionally excluded preview. Removing the global block should not accidentally publish draft routes or replace their access controls.

Add noindex tag checks to CI

Keep a list of public routes whose indexing policy must stay clear. In a project that already uses Playwright Test, this example checks response headers, initial HTML tags, and rendered tags:

import { test, expect } from "@playwright/test";

const blocksIndexing = (value) =>
  /(?:^|[\s,:])(noindex|none)(?=$|[\s,;])/i.test(value);

for (const path of ["/", "/docs/getting-started"]) {
  test(`public indexing policy: ${path}`, async ({ page, request }) => {
    const origin = process.env.PUBLIC_ORIGIN;
    expect(origin, "Set PUBLIC_ORIGIN to the deployed site").toBeTruthy();
    const url = new URL(path, origin).href;

    const response = await request.get(url);
    expect(response.status()).toBe(200);
    expect(response.url()).toBe(url);
    const headers = response.headersArray().filter(
      ({ name }) => name.toLowerCase() === "x-robots-tag",
    );
    for (const { value } of headers) {
      expect(blocksIndexing(value), value).toBe(false);
    }
    const html = await response.text();

    const navigation = await page.goto(url);
    expect(navigation?.status()).toBe(200);
    expect(page.url()).toBe(url);
    // Replace this with a stable signal that your route is ready.
    await expect(page.locator("main h1")).toBeVisible();

    const policies = await page.evaluate((source) => {
      const initial = new DOMParser().parseFromString(source, "text/html");
      const read = (doc) => Array.from(doc.querySelectorAll("meta[name]"))
        .filter((tag) => /^(robots|googlebot)$/i.test(tag.name))
        .map((tag) => tag.content);
      return [...read(initial), ...read(document)];
    }, html);
    for (const value of policies) {
      expect(blocksIndexing(value), value).toBe(false);
    }
  });
}

Configure PUBLIC_ORIGIN in the CI job after deployment. The Playwright APIResponse documentation describes the response methods, including the array that preserves repeated headers.

This is a conservative release contract: it rejects any exclusion header, including one scoped to a different crawler. It is not a complete search-engine policy parser. Adapt that assertion if your site intentionally maintains crawler-specific rules. Add separate tests that require the intended exclusion on preview routes, and use your application's readiness signal so delayed metadata updates are checked.

Keep the test alongside other website regression checks. A route assertion should fail with the actual directive, not only a general SEO score.

Verify the fix in Search Console

Inspect the exact URL in a verified Search Console property. Compare Google's last indexed inspection with a live test. Google's URL Inspection documentation distinguishes those views: the live test checks the current response, while the indexed view reflects Google's stored information.

If the live page is eligible, you can request indexing and monitor the result. Eligibility and a request do not guarantee inclusion or ranking. A previously observed exclusion may remain in reports until another crawl processes the change.

If the live test still finds a block, return to the emitted tags, headers, and crawler access. If no exclusion remains, investigate status codes, canonical selection, rendering, and content quality rather than repeatedly changing the same tag. Use the XML sitemap validation guide to align discovery with your published route inventory.

Check the surrounding launch controls with Slop Audit

Once your noindex tests pass, run Slop Audit on the public page to review complementary launch evidence: reachability, final URL, robots.txt access, canonical metadata, title and description, and bounded sitemap checks. Investigate individual findings and coverage rather than assuming a score proves indexing.

Slop Audit does not currently evaluate robots meta noindex or X-Robots-Tag directives. Keep the response and CI checks above for that policy, and use Search Console for Google's view. The audit adds evidence about the surrounding page; it cannot request indexing or guarantee search visibility.

Before signing off, confirm that published routes have no unintended exclusion, previews retain their intended policy, private routes remain access-controlled, and the deployed response matches the release contract. Save the audit report with those checks so the next deployment has a baseline to compare.