DEV Community

ScientiaMobile CTO
ScientiaMobile CTO

Posted on Originally published at Medium

Is Device Detection Bad for Web Development? We Beg to Differ

By Luca Passani, WURFL Inventor & CTO at ScientiaMobile

In the previous episodes: in 2010, a guy named Ethan Marcotte discovered Responsive Web Design (RWD), i.e. the skillful use of conditional CSS (Media Queries) to make a fully-fledged website turn itself into a column on smartphones and other devices. Since then, Device Detection (disparagingly called "user-agent sniffing" by RWD aficionados) has been the thing you are not supposed to do, if you don't want to be discriminated against. Little did it matter that certain issues were outside of JavaScript's reach…

Server-side detection appears on best-practice lists next to <table> layouts and document.write() (if you get the humor, this article is for you).

"Use feature detection. Use media queries. Let the client decide. The server has no business knowing what is on the other end of the socket"

— Any member of the RWD cult.

This has been the common wisdom from "cool" web developers over the last fifteen years. For all this time, "user-agent sniffing" has been the thing you are not supposed to do.

I invented WURFL back in the day when WAP was not only a thing, but the only thing that would let people access the internet through their mobile phones. In spite of this, WURFL vs. RWD has never been a battle for me. The moment I saw Responsive, I knew it was there to stay. Smartphones had started to outnumber feature phones, and if WURFL vs. Responsive ever was a thing (it wasn't, really), it was obvious that Responsive was going to win. Hands down.

After all, it's hard to compete against the economy of scale that having a single HTML version to deal with delivers. Many years down the line, I have no problem summarizing the common wisdom around the debate. To be clear, some of these issues are exactly why I created WURFL in the first place, but I want all cards on the table now.

  • Inaccuracy: User-agent strings can be spoofed. Many browsers allow users to alter their user-agent strings, leading to incorrect assumptions about capabilities or behaviors.
    Note: WURFL is about accurate device detection.

  • Maintenance Burden: User-agent strings change frequently with new browser versions and devices. This requires continuous updates to the sniffing logic, which can become a significant maintenance burden.
    Note: WURFL solves this. ScientiaMobile constantly updates WURFL for its customers.

  • Cross-Device Compatibility: Relying on user-agent sniffing can lead to inconsistencies in user experience across different devices. This can create a fragmented approach to development, where different browsers/devices may not get the same quality of service.
    Note: Implementing different user experiences for different devices is a good thing. This is one of WURFL's main use-cases still today.

  • Complexity: User-agent detection adds unnecessary complexity to the codebase. This can lead to increased chances of bugs and makes debugging more difficult.
    Note: Not really. WURFL is about looking up a property at the end of the day.

  • Performance Issues: The overhead of parsing user-agent strings can affect the performance of the web application, especially if done frequently or in a resource-intensive manner.
    Note: parsing effort happens server-side in the case of WURFL.

  • Future-proofing: As new devices and browsers emerge, adapting to a static set of user-agent strings becomes impractical. Using feature detection or responsive design techniques is often more robust and adaptable.
    Note: Moot. New devices and browsers break Responsive sites and applications as well.

  • Best Practices: Modern web development emphasizes progressive enhancement, graceful degradation, and feature detection (for example, using libraries like Modernizr) instead of relying on user-agent strings, thus promoting more reliable and maintainable code.
    Note: yes, no, maybe… This is where things get a bit religious in my opinion. Not going there for now, but please keep reading.

The argument against sniffing was never really about knowing the device. It was about guessing the device — a regex in a PHP file, written once in 2011, still shipping in 2019, still deciding that an iPad is a phone because the string Mobile appears in its user agent. That criticism was correct. It is still correct. My point is that WURFL (and WURFL.js Business Edition in particular) is something else.

Put your code where your mouth is

To demonstrate this position, we did not build a slide deck. We built a website. A real, free, public tool with real visitors, where every device-dependent decision is visible from outside.

onlineimageoptimizer.com

Free Online Image Optimizer (OIO for friends) is an image and PDF utility site. Users don't need to register an account, images don't have watermarks and nobody will try to upsell them on anything. It's a real site for users, and a demo site for our technologies: WURFL, WURFL.js and ImageEngine.

It's built as a Vue.js 3 application fronted by ScientiaMobile's ImageEngine CDN, and it wears more than one face: a website and an SPA (Single Page Application) and a PWA (Progressive Web Application), installable as an app on Android and iOS. The linchpin of the service is a drop zone that compresses, converts, resizes, flips, darkens, watermarks and captions images, handles batches of up to 20, and turns PDFs into images (or photos into a single PDF).

Multiple task pages are generated from a registry, each a permanent keyword-specific entrance to that same application with a preset configuration — /jpg-to-webp, /pdf-to-jpg, /remove-exif-data, /compress-photos-for-email, /meme-generator, /compress-scanned-documents and so on (full list of OIO features here).

OIO tools overview

Figure: Online Image Optimizer (OIO) offers many nifty tools for image and PDF.

OIO is a live demonstration of device-adaptive delivery: WURFL.js identifies the device, which changes the interface (drag-and-drop copy versus phone copy, clipboard paste only where a clipboard exists, hover previews on desktop and press-and-hold on tablets), while ImageEngine serves a different image from the same URL depending on who asks — measured earlier at a 2.9 MB master coming back as a 28 KB AVIF on a Google Pixel and a 145 KB AVIF on a desktop.

Here are three user-experiences tailored for the form factor:

Online Image Optimizer Desktop UX

Figure: Desktop OIO won't offer the user to take a picture, but will inform them that CTRL/CMD +V will paste the image directly into the dropzone.

Online Image Optimizer Tablet UX

Figure: OIO for tablet won't offer the desktop shortcuts, but inform the user that taking a picture is totally feasible (I'd like to see how the feature detection guys understand that it's an iPad).

Online Image Optimizer Smartphone UX

Figure: Smartphone users get a less cluttered UX. Not all features available on desktop make sense on a mobile phone.

This is made possible by WURFL.js

WURFL.js ain't your grandfather's user-agent sniffing

Once you have a WURFL.js Business Edition account, integration is easy:

const wurfljs = document.createElement("script")
wurfljs.setAttribute("src", "//wjs.wurflcloud.com/wurfl.js")
document.head.appendChild(wurfljs)
Enter fullscreen mode Exit fullscreen mode

That looks like a client-side library, I know. It is not. It's a request. The browser asks wjs.wurflcloud.com for a script; our servers run the full WURFL engine against that request's headers; and what comes back is not a parser — it is an answer, already resolved, as a literal object.

Here is the real response body for a Pixel 8 Pro, straight off the wire:

WURFL = {
  advertised_browser: "Chrome Mobile",
  advertised_browser_version: "126.0.0.0",
  advertised_device_os: "Android",
  advertised_device_os_version: "14",
  brand_name: "Google",
  complete_device_name: "Google Pixel 8 Pro",
  form_factor: "Smartphone",
  is_android: true,  is_app_webview: false, is_full_desktop: false,
  is_ios: false,     is_mobile: true,       is_robot: false,
  is_smartphone: true, is_smarttv: false,   is_tablet: false,
  max_image_height: 640, max_image_width: 320,
  model_name: "Pixel 8 Pro",
  physical_screen_height: 156, physical_screen_width: 70,
  pointing_method: "touchscreen",
  resolution_height: 2992, resolution_width: 1344
}
Enter fullscreen mode Exit fullscreen mode

Note: in the case of iOS devices, the response is quite a bit more complex. Apple, in its infinite wisdom, has decided to make publishers' lives difficult. But don't worry. Also in the case of iOS devices, you'll end up with an object like the one above. Google has been equally nasty, as they decided to freeze the Chrome Browser User-Agent string a few years ago, and replace it with a complex HTTPS dance to request the most meaningful extra headers (i.e. UACH, or UA Client-Hints). WURFL.js performs the dance on your behalf.

Twenty-six capabilities. 1.9 KB gzipped. The detection is server-side; only the consumption is in the browser, because this particular site is a static SPA on a CDN and has no origin request to hang a decision on. In a Django, Rails, Spring or Node application you would put WURFL InFuze or OnSite in the request path instead and render the right page the first time — same engine, no repaint. The architecture is a deployment choice. The knowledge is the product.

Two details worth noticing in that snippet, because they are the whole post in miniature.

physical_screen_width: 70 — millimetres. There is no browser API for that. window.screen gives you pixels, and a pixel has not meant a fixed physical size since 2010. If you want to know how big the thing in someone's hand actually is, a database is the only route.

form_factor: "Smartphone" — one of eight values (Desktop, Tablet, Smartphone, Feature Phone, Smart-TV, Robot, Other non-Mobile, Other Mobile). This is the field our interface actually branches on, and it is deliberately coarse. We are not personalizing to a model. We are answering what kind of thing is this, which is a question with eight sane answers and no privacy story.

Of course, knowing the device is only interesting if it changes something. Here is every place it changes something on our site, and none of them are cosmetic.

1. The drop zone tells the truth about the gesture.

<span v-show="!files.length && !myWURFLData.isMobile">{{ words.dropzone }}</span>
<span v-show="!files.length && myWURFLData.isMobile">{{ says('dropzone.mobileHeadline') }}</span>
Enter fullscreen mode Exit fullscreen mode

"Drag your images here" is a lie on a phone. There is nothing to drag from.

Note: I almost need to pause for a second here to make you observe what an incredibly elegant and powerful pair WURFL.js and Vue.js conditionals is. WURFL.js tells Vue what the thing is. Vue decides what to do about it. That's the clean separation that makes adaptive UIs manageable and frankly a pleasure to code.

2. Clipboard paste is offered only where a clipboard exists.

<template v-if="myWURFLData.isDesktop && accepted.images">
  {{ t('dropzone.paste') }} <b>{{ t('dropzone.pasteKeys') }}</b>
</template>
Enter fullscreen mode Exit fullscreen mode

Advertising ⌘+V or CTRL+V to a phone is not a graceful degradation. It is a dead end printed in bold.

3. Instructions are inverted by form factor. Desktop gets a three-step "How it works" list expanded. A phone gets a collapsed link, because on a 360px screen an expanded list pushes the only control that matters below the fold.

4. The <picture> element tutorial is hidden on phones. Our responsive-image tool prints the markup you should paste into your HTML. That is desktop work. On a phone it is a wall of angle brackets between you and your file.

5. And the one that made the case internally — the three-way gesture. Hovering a result thumbnail shows it large. This is the comment I wrote in the code, and I would like it to be the last word on why form_factor beats a boolean:

/* Who gets to look at a thumbnail properly.
 *
 * A mouse hovers, so a desktop shows the preview on hover. A tablet cannot
 * hover, so it gets press-and-hold instead. A phone gets neither: the screen is
 * barely bigger than the preview, and press-and-hold there collides with
 * scrolling a long table.
 *
 * Called again below once WURFL.js has answered, because the regexes above are
 * a guess and this is exactly the kind of thing they guess wrong -- an iPad
 * reporting a desktop user agent would otherwise be given a gesture it cannot
 * perform.
 */
function applyPeekGestures() {
    configurePeek({
        hover: !!myWURFLData.value.isDesktop,
        hold:  !!myWURFLData.value.isTablet,
    })
}
Enter fullscreen mode Exit fullscreen mode

Three outcomes: hover, hold, neither. @media (hover: hover) gives you two, and gets the iPad wrong, because an iPad in desktop mode reports a Macintosh user agent and supports neither gesture well. There is no CSS query for "this is a tablet".

PDF to images, desktop

Figure: Converting a PDF file into images (desktop UX — 1 of 2)

PDF to images, hovering on thumbnail, desktop

Figure: Converting a PDF file into images. Hovering on thumbnail (desktop UX — 2 of 2)

PDF to images, smartphone

Figure: Converting a PDF file into images. Hovering on thumbnail (smartphone UX)

And these are just a few examples. There are plenty of other places where device information supports key functionalities. You don't need to scroll to see how your actions are reflecting on other parts of the UI on a desktop. On a mobile device, when the time comes you'll need to teleport the user to an area below the fold, where the goodies (the PDF pages rendered as JPEGs, for example) are.

Conclusion

Responsive won as the most suitable approach to web design. I knew it would the moment I saw it, and I always said so. One HTML version, conditional CSS, an economy of scale nobody can argue with. I don't think there was ever a real competition between responsive and the server-side approach. I never encouraged that fight, and if there was a fight, I wasn't actively participating.

But RWD won layout. It did not win everything. ImageEngine proves it and so does WURFL.js. There are use-cases where device detection is still needed. Programmatic advertising and analytics are the clearest examples, but optimizing the user experience can benefit from understanding the device as well.

A media query can tell you the viewport is 390 pixels wide. It cannot tell you the screen is 70 millimetres across. Feature detection can tell you hover is supported — it cannot tell you this is an iPad, where hovering is technically available and practically useless. Accept: image/avif can tell you the browser decodes AVIF. It cannot tell you that this phone wants 1344 pixels and not 2560.

OIO meme generator

Figure: OIO can also generate fun memes, like the one I used for the header of this article.

Those aren't edge cases. They are the difference between a site that works on a phone and a site that was built for one. The anti-pattern was never "the server has no business knowing what is on the other end of the socket". The anti-pattern is pretending the other end of the socket is a single abstract device — that a 70 × 156 mm touchscreen on metered data and a 27-inch monitor on fibre are the same runtime with different CSS.

They are not. Every difference between them is knowable. Some of it is only knowable from a database — and maintaining that database has been my life's work since WAP phones.

Sniffing was never the mistake. Guessing was. With WURFL, you can stop guessing.

The site is onlineimageoptimizer.com — free, no account, and the source of every code snippet above. WURFL.js: web.wurfl.io. ImageEngine: imageengine.io. Every measurement in this post was taken with curl against the live endpoints on 4 September 2026 and is reproducible.

Top comments (0)