Skip to content
Switch branches/tags

Latest commit


Git stats


Failed to load latest commit information.
Latest commit message
Commit time

Serving differential builds with EmberJS


There is currently no way to do differential serving of assets based on the user's browser in Ember land. The closest thing currently is a pre-RFC.

This is one feasible solution to this problem. Importantly,

  • This solution tries to address engines as well. With the usual type="module" and nomodule approach, you cannot handle engines as they rely on the asset manifest provided by a meta tag with name as app/config/asset-manifest. There is no way to toggle meta tags in the frontend as of now and until Ember Engines support the storeConfigInMeta option, you will either have to do something similar to this solution or not do differential serving of engine assets alone. PS: There is a long-pending PR to implement the storeConfigInMeta option in Ember Engines.
  • This solution requires you to serve different bundles by manipulating the HTML at runtime in the server based on a cookie, or if possible, parsing the UA.


  • In your index.html, add a data-for="ember" attribute to script tags that are generated by Ember. For example,
      <script data-for="ember" src="{{rootURL}}assets/vendor.js"></script>
      <script data-for="ember" src="{{rootURL}}assets/app.js"></script>
  • In your application code, run a small script to set a cookie to determine if the user is currently using a modern or legacy browser.
      // Serve modern build if Promise and async/await are present
      let testCode = 'async() => { let p = new Promise(); await p(); }';
      try {
        // this needs to be a new Function/eval because otherwise,
        // you will get Syntax Errors in the code
        (new Function(testCode))();
        // Set a never-expiring cookie
      } catch(err) {
        // Set a cookie with a short validity(say a month)
        // If the user updates their browser, it will be reflected
        // once the cookie expires
  • In your targets.js file, use the process.env.LEGACY flag to toggle support for legacy browsers.
      'use strict';
      let browsers = [
        'last 1 Chrome versions',
        'last 1 Firefox versions',
        'last 1 Safari versions'
      const isCI = !!process.env.CI;
      const isProduction = process.env.EMBER_ENV === 'production';
      const isLegacyBuild = !!process.env.LEGACY;
      if (isCI || isProduction || isLegacyBuild) {
        browsers = [
          'Chrome >= 42',
          'Firefox >= 39',
          'Edge >= 14',
          'Safari >= 10'
          'ie 11'
      module.exports = {
  • broccoli-persistent-filter does not support concurrent builds. This will result in build errors with the message Unexpected end of file .... To avoid this, in ember-cli-build.js, set the BROCCOLI_PERSISTENT_FILTER_CACHE_ROOT environment variable to a random path. For example,
      if (EmberApp.env() === 'production') {
        process.env.BROCCOLI_PERSISTENT_FILTER_CACHE_ROOT = path.join(
         `cache_${}_${Math.floor(Math.random() * 10)}`
  • Run parallel builds with
      ember build --environment=production --output-path=modern &
      LEGACY=true ember build --environment=production --output-path=legacy
  • Run the index.js in this repo. It will generate a dist folder with an index.html containing combined script tags and meta tags wrapped in <MODERN> and <LEGACY> tags.
  • In your server, detect the presence of the cookie that was previously set (or use the UA) and replace the tags accordingly.
      // if modern browser
      replaceAll('<LEGACY>.*</LEGACY>', '');
      replaceAll('<MODERN>(.*)</MODERN>', '$1');
      // if legacy browser
      replaceAll('<MODERN>.*</MODERN>', '');
      replaceAll('<LEGACY>(.*)</LEGACY>', '$1');


An approach to serving differential bundles in Ember



No releases published


No packages published