What rel=nofollow Actually Does — Measured in a Real Browser

The usual explanation is about PageRank, which is unobservable. The part that is observable is the HTTP request, and almost every statement about nofollow conflates the two. So I served a page with five links differing only in their rel attribute, clicked each in Chrome, and recorded the Referer the destination received.

14 November 2026 · Chrome 141 via CDP · two local origins

The claim under test

Common statements about rel="nofollow", in descending order of confidence:

  1. It tells search engines the link is not an endorsement. This is what the spec says.
  2. It does not stop the click from working, or stop the visit from being counted by the destination. Testable.
  3. It does not change what the browser sends in the request. Testable, and I had never seen it tested.
  4. It prevents the link from passing PageRank. Not observable from outside Google.

Point 4 is the one everybody argues about and nobody can measure. Points 2 and 3 are measurable in a few minutes, and they determine whether a nofollow link is worth placing for traffic reasons — a separate question from its ranking effect.

Experiment 1 — five rel variants, same target

Two http.server instances on 127.0.0.1:8765 and :8766. Because the port differs, the browser treats them as different origins, which is what exercises the cross-origin referrer rules. The page on 8765 contains ten links: five rel variants pointing at 8765, and the same five pointing at 8766. Every link is otherwise byte-identical.

LINKS = [
    ("plain",         ""),
    ("nofollow",      ' rel="nofollow"'),
    ("nofollow_ugc",  ' rel="nofollow ugc"'),
    ("noreferrer",    ' rel="noreferrer"'),
    ("noopener",      ' rel="noopener"'),
]

Each link was clicked programmatically through the DevTools Protocol, and the destination logged self.headers.get("Referer"). Results, for a click from http://127.0.0.1:8765/ to http://127.0.0.1:8766/target:

rel on the linkReferer received by destination Header present
(none)http://127.0.0.1:8765/yes
nofollowhttp://127.0.0.1:8765/yes
nofollow ugchttp://127.0.0.1:8765/yes
noreferrerabsentno
noopenerhttp://127.0.0.1:8765/yes

Same-origin results were identical except that the full path was included (/target?k=plain etc.), which is the expected path-level difference between same-origin and cross-origin defaults.

The finding is unambiguous: rel="nofollow" does not alter the request in any way. The destination receives exactly the same Referer, and the same Sec-Fetch-Site: same-site, as a plain link. If a site owner reads their referrer log, a nofollow visit is indistinguishable from a followed one. Only noreferrer strips it — and noreferrer also implies noopener, which is a different feature entirely.

So "nofollow links still pass traffic" is correct, and for a duller reason than people assume. The browser never knew or cared about the rel value when building the request. The click is a navigation like any other. The rel attribute is metadata consumed after the fact, by crawlers, not a transport modifier.

Experiment 2 — what actually removes the referrer

If rel does not govern this, Referrer-Policy does. Same setup, except the source page is served with a different Referrer-Policy response header per run, on 8775 → 8776:

Referrer-Policy on sourceReferer received cross-origin
no-referrerabsent
originhttp://127.0.0.1:8775/
origin-when-cross-originhttp://127.0.0.1:8775/
strict-origin-when-cross-originhttp://127.0.0.1:8775/
unsafe-urlhttp://127.0.0.1:8775/?pol=unsafe-url

This is the matrix that matters operationally. A site that wants referrer credit for outbound links gets it under the browser default (strict-origin-when-cross-origin, which strips the path but keeps the host), and loses it entirely under no-referrer. Note the unsafe-url row: the path survives cross-origin only under that policy, which is why full referrer paths are so rare in analytics.

Checking the two domains in this cluster directly with curl -I:

HostReferrer-Policy sentEffect on outbound clicks
celticgemini001.github.ionot set (browser default) host sent, path stripped
upgolab.comno-referrer nothing sent at all

That second row is worth sitting with. A site that sends Referrer-Policy: no-referrer is invisible in the referrer logs of everything it links to. If you are judging a link-building effort by destination-side referrer reports, links from such a site will look like direct traffic. The visits are real; the attribution is gone. Any measurement of off-site linking that relies on destination referrers is therefore blind to every source site configured this way — which is a meaningful and growing share of the web.

What this changes about placing links

Reproducing it

# two origins: different port = different origin for referrer purposes
python3 - <<'PY'
import http.server, threading, json
class H(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        print(json.dumps({"port": self.server.server_address[1],
                          "path": self.path,
                          "referer": self.headers.get("Referer")}))
        body = b'<a id=t href="http://127.0.0.1:8766/target" rel=nofollow>go</a>'
        self.send_response(200); self.send_header("Content-Length", str(len(body)))
        self.end_headers(); self.wfile.write(body)
for p in (8765, 8766):
    threading.Thread(target=http.server.ThreadingHTTPServer(("127.0.0.1", p), H)
                     .serve_forever, daemon=True).start()
threading.Event().wait()
PY

# then click it in a real browser and read the referer the second server printed

The measurement is four lines of server and one click. It is worth running once, because the result is counter-intuitive in a way that reading the spec does not fix: the rel attribute simply is not consulted when the request is built.

The tool these notes come from

This cluster of notes exists because claims about images and about links are both routinely asserted without measurement. If what you actually need is to upscale a photo or a short video to 2K or 4K in the browser, that is the tool the image measurements were taken against — and the same habit applies: run it on your own file, then compare, rather than trusting the preset name.