The Browser Wars and the Rise of Sniffing
User agent sniffing became standard practice during a period when two browsers implemented the same features differently enough that one page could not serve both. The technique survived long after that stopped being true.
Incompatible implementations forced a branch
Scripting interfaces, layout behaviour and document models diverged sharply between competing browsers. A site that used any of them had to write two versions of the affected code and choose between them.
The only signal available at the time was the user agent header, which arrived with the request and cost nothing to inspect. Choosing on that basis was the obvious engineering move.
Nothing about it was reckless given the tools available. Feature testing from script was possible but poorly understood, and server-side branching happened before any markup was sent.
Branching hardened into gatekeeping
What began as choosing between two code paths turned into refusing unrecognised browsers entirely. A site that only tested against two products treated everything else as unsupported.
Users of smaller browsers met blocking pages even when the underlying engine would have rendered the site correctly. The check was on the name, not on any capability the name implied.
This is the point at which sniffing became a compatibility problem rather than a compatibility solution. The signal was being used to make decisions it could not support.
Minority browsers responded by imitating
A browser locked out by a name check has two options: persuade every site to update, or change the name it sends. The second is unilateral and works immediately.
Smaller vendors adopted the tokens of larger ones, sometimes appending their own identity at the end where naive checks would not look. Access improved and the string became less truthful.
Every imitation degraded the header for everyone. Sites tightened their checks, browsers added more tokens, and each round left the string longer and less able to identify anything.
Feature detection arrived as the alternative
Testing whether a capability exists, rather than inferring it from a name, removes the entire chain of guesswork. The test asks the question the code actually needs answered.
Adoption was slow because feature detection requires script to run and produces answers after the page has begun loading. Server-side branching gave answers before a byte was sent.
The performance argument kept sniffing alive in server-rendered stacks well past the point where the compatibility argument had evaporated. Habit and infrastructure both resist replacement.
The residue is still in production
Code written during the divergent period remains in content management systems, templates and shared libraries that nobody has reason to open. It runs against browsers its authors never anticipated.
Such checks fail in a particular direction. They recognise the historical names and treat everything newer as unknown, so recent browsers receive the fallback intended for obsolete ones.
That inversion is the lasting cost. A technique adopted to serve users better now most reliably penalises the users running the most current software.