Skip to content

Detecting Outdated Browsers in Vanilla JavaScript

Detect outdated browsers, such as Internet Explorer, with a handy JavaScript util.

Detecting Outdated Browsers in Vanilla JavaScript

I recently built a simple banner in a React app that informed users if they use an outdated browser, such as Internet Explorer 11. Initially, I thought that this task would be relatively simple, but surprisingly it took me quite a bit to figure out the best solution (for this case). So let's have a look at how I solved it.

Requirements

  • The banner should only display when a user visits the app using an outdated browser.
  • The banner should point users to a website informing them about their browser situation. I went with this site.
  • The company I work for has a spreadsheet with supported and unsupported browsers and their versions, so this had to be matched in the code. For example, Chrome 61 is classified as outdated, whereas Chrome 62 is (still) fine.
  • I wanted to have a simple API that gets me the browser's name and its current version.

The Research

Upon my investigation, I encountered https://browser-update.org/. They provide a lightweight JavaScript library and automatically add an already styled banner to your website (although you can customize it). However, I needed to integrate said banner into a React component and figured that it would be easier not to use this library.

I also found some other third-party scripts doing more or less the same kind of work, but in the end, I decided that I didn't want to include any more external JavaScript in the app.

I stumbled upon this thread while casually looking through StackOverflow for some ideas. The accepted answer from Dhanil looked promising to me, so I decided to give it a try and (literally) wrap it up.

The Solution

Again, the below snippet is not mine; it's from a guy called Dhanil on StackOverflow. Credit to him for providing this handy piece of code.

var browser = (function () {
  var ua = navigator.userAgent,
    tem,
    M = ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];

  if (/trident/i.test(M[1])) {
    tem = /\brv[ :]+(\d+)/g.exec(ua) || [];
    return { name: "IE", version: tem[1] || "" };
  }

  if (M[1] === "Chrome") {
    tem = ua.match(/\b(OPR|Edge)\/(\d+)/);
    if (tem != null) {
      return { name: tem[1].replace("OPR", "Opera"), version: tem[2] };
    }
  }

  M = M[2] ? [M[1], M[2]] : [navigator.appName, navigator.appVersion, "-?"];

  if ((tem = ua.match(/version\/(\d+)/i)) != null) {
    M.splice(1, 1, tem[1]);
  }

  return { name: M[0], version: M[1] };
})();

The above script gets the user agent string and applies some tests using regular expressions to figure out the browser's name and its version. If you have ever worked with user agents, you might know what kind of pain you're dealing with, so it surprised me quite a bit how well this script works.

However, I wanted a global class that would provide me with a convenient API, like isSupported() or isIE(). Here's the result:

/**
 * BrowserDetector
 *
 * This util checks the current browser name and version and offers a
 * convinient API to test for specific versions or browsers and whether
 * the current visitor uses a supported browser or not.
 */
export default class BrowserDetector {
  constructor() {
    this.browser = {};
    this.unsupportedBrowsers = {
      Chrome: 70,
      Firefox: 60,
      IE: 10,
      Edge: 15,
      Opera: 50,
      Safari: 12,
    };

    this._detectBrowser();
  }

  /**
   * Detects the current browser and its version number.
   *
   * @returns {Object} An object with keys for browser `name` and `version`.
   */
  _detectBrowser() {
    this.browser = (function () {
      var ua = navigator.userAgent,
        tem,
        M = ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];

      if (/trident/i.test(M[1])) {
        tem = /\brv[ :]+(\d+)/g.exec(ua) || [];
        return { name: "IE", version: tem[1] || "" };
      }

      if (M[1] === "Chrome") {
        tem = ua.match(/\b(OPR|Edge)\/(\d+)/);
        if (tem != null) {
          return { name: tem[1].replace("OPR", "Opera"), version: tem[2] };
        }
      }

      M = M[2] ? [M[1], M[2]] : [navigator.appName, navigator.appVersion, "-?"];

      if ((tem = ua.match(/version\/(\d+)/i)) != null) {
        M.splice(1, 1, tem[1]);
      }

      return { name: M[0], version: M[1] };
    })();
  }

  /**
   * Checks if the current browser is Internet Explorer.
   *
   * @returns {Boolean}
   */
  get isIE() {
    return this.browser.name === "IE";
  }

  /**
   * Checks if the current browser is Edge.
   *
   * @returns {Boolean}
   */
  get isEdge() {
    return this.browser.name === "Edge";
  }

  /**
   * Checks if the current browser is from Microsoft (Edge
   * or Internet Explorer).
   *
   * @returns {Boolean}
   */
  get isMicrosoft() {
    return this.isIE || this.isEdge;
  }

  /**
   * Checks if the current browser is Firefox.
   *
   * @returns {Boolean}
   */
  get isFirefox() {
    return this.browser.name === "Firefox";
  }

  /**
   * Checks if the current browser is Chrome.
   *
   * @returns {Boolean}
   */
  get isChrome() {
    return this.browser.name === "Chrome";
  }

  /**
   * Checks if the current browser is Safari.
   *
   * @returns {Boolean}
   */
  get isSafari() {
    return this.browser.name === "Safari";
  }

  /**
   * Checks if the current browser is from an Android device.
   *
   * @returns {Boolean}
   */
  get isAndroid() {
    return /Android/i.test(navigator.userAgent);
  }

  /**
   * Checks if the current browser is from a BlackBerry device.
   *
   * @returns {Boolean}
   */
  get isBlackBerry() {
    return /BlackBerry/i.test(navigator.userAgent);
  }

  /**
   * Checks if the current browser is from a Windows Mobile device.
   *
   * @returns {Boolean}
   */
  get isWindowsMobile() {
    return /IEMobile/i.test(navigator.userAgent);
  }

  /**
   * Checks if the current browser is Mobile Safari.
   *
   * @returns {Boolean}
   */
  get isIOS() {
    return /iPhone|iPad|iPod/i.test(navigator.userAgent);
  }

  /**
   * Checks if the current browser is a mobile browser.
   *
   * @returns {Boolean}
   */
  get isMobile() {
    return this.isAndroid || this.isBlackBerry || this.isWindowsMobile || this.isIOS;
  }

  /**
   * Checks if the current browser is supported by
   * our environment.
   *
   * @returns {Boolean}
   */
  isSupported() {
    if (this.unsupportedBrowsers.hasOwnProperty(this.browser.name)) {
      if (+this.browser.version > this.unsupportedBrowsers[this.browser.name]) {
        return true;
      }
    }

    return false;
  }
}
  • Note that in the constructor, I provide an object with all the browsers and their versions without support. If the browser version is higher than that number, it's supported; lower or equal means it's not. Of course, you could retrieve this information from an endpoint or a JSON inside your project, but in my case, it's just fine as is.
  • The method _detectBrowser() is meant to be a private method and is only called once in the constructor, populating the this.browser property.
  • You now have access to a public API by calling the methods, such as isFirefox or isSupported(). You could include this class in whatever part of your application and make decisions based on it.
if (new BrowserDetector().isIE) {
  toggleOutdatedBrowserMessage(); // Or whatever...
}

If you want to see an actual example of how you could use the above script in a React app, you might want to have a look at this snippet:

import { Component } from 'react;
import { render } from 'react-dom';

import BrowserDetector from './browser-detector.js';

function TopBanner(props) {
  if (!props.open)
    return null;
  }

  return (
    <div role="alert">
      <strong>Outdated browser detected!</strong>
      Consider upgrading or switching to a <a href="http://outdatedbrowser.com/en">different one</a>.
    </div>
  );
}

class App extends Component {
  state = {
    outdatedBrowser: false
  }

  componentDidMount() {
    if ((new BrowserDetector).isSupported()) {
      this.setState({
        outdatedBrowser: true
      });
    }
  }

  render() {
    return (
      <>
        <TopBanner open={this.state.outdatedBrowser} />
        <h1>Hello World!</h1>
      </>
     );
  }
}

render(<App />, document.getElementById('root'));

You can see one functional component called TopBanner and the main class component called App. App includes the TopBanner and provides it a prop called open, which can either be true or false. In the App component, we use a lifecycle method called componentDidMount, which is called each time the component is mounted into the DOM. If that's the case, it will run our little browser detector script and set the local state, therefore toggling the TopBanner.

Inside the TopBanner component, I've just put a single conditional that checks whether open is set to true or false. In the second case, nothing gets rendered into the DOM, as if the component doesn't exist. Otherwise, we display the message, which could be extended by a close button and some styling.

Conclusion

The above code is pretty easy and reliable to detect browsers and their versions. Of course, you might want to use an already existing library (like this one), but I prefer not to add more dependencies than necessary.

Final note: Please don't make the same error as other (major) web applications do and lockout users based on what kind of browser they use. If possible, use [feature detection](https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Feature_detection rather than browser detection because locking out all Edge users just because Edge doesn't support one API you use is not a good move. If a particular browser is missing a feature you are using in your app, consider just deactivating said feature, but leave the rest of the site accessible.

In my case, I don't block any user because of their browser but just display a simple, informative message to them. If a user visits the app with IE9, well, he won't get far anyway.