diff --git a/facets.libraries.yml b/facets.libraries.yml
index 2ebbf32..a77986d 100644
--- a/facets.libraries.yml
+++ b/facets.libraries.yml
@@ -53,3 +53,12 @@ soft-limit:
     - core/jquery.once
     - core/drupal
     - core/drupalSettings
+drupal.facets.views-ajax:
+  js:
+    js/facets-views-ajax.js: {}
+  dependencies:
+    - core/jquery
+    - core/jquery.once
+    - core/drupal
+    - core/drupalSettings
+    - core/drupal.ajax
diff --git a/facets.routing.yml b/facets.routing.yml
index 1e897e7..9c08b77 100644
--- a/facets.routing.yml
+++ b/facets.routing.yml
@@ -48,3 +48,9 @@ entity.facets_facet_source.edit_form:
     _title: 'Edit facet source configuration'
   requirements:
     _entity_create_access: 'facets_facet'
+facets.block.ajax:
+  path: '/facets-block/ajax'
+  defaults:
+    _controller: '\Drupal\facets\Controller\FacetBlockAjaxController::ajaxFacetBlockView'
+  requirements:
+    _access: 'TRUE'
diff --git a/js/checkbox-widget.js b/js/checkbox-widget.js
index 154c54b..47744de 100644
--- a/js/checkbox-widget.js
+++ b/js/checkbox-widget.js
@@ -43,7 +43,7 @@
 
     checkbox.on('change.facets', function (e) {
       Drupal.facets.disableFacet($link.parents('.js-facets-checkbox-links'));
-      window.location.href = $(this).data('facetsredir');
+      $(this).siblings('a').trigger('click');
     });
 
     if (active) {
@@ -51,7 +51,7 @@
       label.find('.js-facet-deactivate').remove();
     }
 
-    $link.before(checkbox).before(label).remove();
+    $link.before(checkbox).before(label).hide();
 
   };
 
diff --git a/js/dropdown-widget.js b/js/dropdown-widget.js
index 5d599f1..47b5103 100644
--- a/js/dropdown-widget.js
+++ b/js/dropdown-widget.js
@@ -67,7 +67,7 @@
       }
 
       // Replace links with dropdown.
-      $ul.after($dropdown).remove();
+      $ul.after($dropdown).hide();
       Drupal.attachBehaviors($dropdown.parent()[0], Drupal.settings);
     });
   };
diff --git a/js/facets-views-ajax.js b/js/facets-views-ajax.js
new file mode 100644
index 0000000..2e4dd81
--- /dev/null
+++ b/js/facets-views-ajax.js
@@ -0,0 +1,133 @@
+/**
+ * @file
+ * Facets Views AJAX handling.
+ */
+
+/**
+ * @name FacetsViewsAjaxSettings
+ * @property {String} view_id
+ * @property {String} current_display_id
+ * @property {String} view_base_path
+ */
+
+/**
+ * @property {FacetsViewsAjaxSettings[]} drupalSettings.facets_views_ajax
+ */
+
+
+(function ($, Drupal) {
+  'use strict';
+
+  /**
+   * Trigger views AJAX refresh on click.
+   */
+  Drupal.behaviors.facetsViewsAjax = {
+    attach: function (context, settings) {
+      var update_summary = false;
+      if (settings.facets_views_ajax.facets_summary_ajax) {
+        update_summary = true;
+      }
+      $.each(settings.facets_views_ajax, function (facetId, facetSettings) {
+
+        // Get the View for the current facet.
+        var view = $('.view-id-' + facetSettings.view_id + '.view-display-id-' + facetSettings.current_display_id).first();
+        var dom_id_start = 'js-view-dom-id-';
+        var current_dom_id = $.map(view.attr('class').split(' '), function (v, i) {
+          if (v.indexOf(dom_id_start) > -1) {
+            return v.slice(dom_id_start.length, v.length);
+          }
+        });
+
+        if (typeof Drupal.views.instances['views_dom_id:' + current_dom_id] === 'undefined') {
+          return;
+        }
+
+        // Get all ajax facets block from the current page.
+        var facets_blocks = [];
+        $('.block-facets-ajax').each(function (index) {
+          var dom_id_start = 'js-facet-dom-id-';
+          var facet_block_id = $.map($(this).attr('class').split(' '), function (v, i) {
+            if (v.indexOf(dom_id_start) > -1) {
+              return v.slice(dom_id_start.length, v.length);
+            }
+          }).join();
+          facets_blocks.push(facet_block_id);
+        });
+
+        if (update_summary && (facetId === 'facets_summary_ajax')) {
+          $('[data-drupal-facets-summary-id=' + facetSettings.facets_summary_id +']').children('ul').children('li').once().click(function (e) {
+            e.preventDefault();
+            var facetLink = $(this).find('a');
+            updateFacetsView(facetLink, facets_blocks, current_dom_id, update_summary, settings);
+          });
+        }
+        else {
+          $('[data-drupal-facet-id=' + facetId + ']').children('.facet-item').once().click(function (e) {
+            e.preventDefault();
+            updateFacetsView($(this), facets_blocks, current_dom_id, update_summary, settings);
+          });
+        }
+      });
+    }
+  };
+
+  // Helper function to update views output & Ajax facets.
+  var updateFacetsView = function (facetLink, facets_blocks, current_dom_id, update_summary_block, settings) {
+    var href = facetLink.attr('href');
+    if (href == undefined) {
+      href = $(facetLink).find('a').attr('href');
+    }
+    var views_parameters = Drupal.Views.parseQueryString(href);
+    var views_arguments = Drupal.Views.parseViewArgs(href, 'search');
+    var views_settings = $.extend(
+      {},
+      Drupal.views.instances['views_dom_id:' + current_dom_id].settings,
+      views_arguments,
+      views_parameters
+    );
+
+    // Update View.
+    var views_ajax_settings = Drupal.views.instances['views_dom_id:' + current_dom_id].element_settings;
+    views_ajax_settings.submit = views_settings;
+    views_ajax_settings.url += '?q=' + href;
+
+
+    // Update facet blocks.
+    var facet_settings = {
+      url: Drupal.url('facets-block/ajax'),
+      submit: {
+        facet_link: href,
+        facets_blocks: facets_blocks
+      }
+    };
+
+    Drupal.ajax(views_ajax_settings).execute();
+
+    if (update_summary_block) {
+      var facet_summary_wrapper_id = $('[data-drupal-facets-summary-id=' + settings.facets_views_ajax.facets_summary_ajax.facets_summary_id + ']').attr('id');
+      var facet_summary_block_id = '';
+      if (facet_summary_wrapper_id.indexOf('--') !== -1) {
+        facet_summary_block_id = facet_summary_wrapper_id.substring(0, facet_summary_wrapper_id.indexOf('--')).replace('block-', '');
+      }
+      else {
+        facet_summary_block_id = facet_summary_wrapper_id.replace('block-', '');
+      }
+      facet_settings.submit.update_summary_block =  update_summary_block;
+      facet_settings.submit.facet_summary_block_id = facet_summary_block_id;
+      facet_settings.submit.facet_summary_wrapper_id = settings.facets_views_ajax.facets_summary_ajax.facets_summary_id;
+    }
+    facet_settings.submit.active_facet = facetLink.closest('.block-facets-ajax').attr('data-block-plugin-id');
+
+    Drupal.ajax(facet_settings).execute();
+  };
+
+  $.fn.replaceFacets = function(data) {
+    if (data.replaceWith === '') {
+      $(data.selector).html('');
+    }
+    else {
+      $(data.selector).replaceWith(data.replaceWith);
+    }
+  };
+
+})(jQuery, Drupal);
diff --git a/modules/facets_summary/src/Plugin/Block/FacetsSummaryBlock.php b/modules/facets_summary/src/Plugin/Block/FacetsSummaryBlock.php
index 93ccd99..f544f6d 100644
--- a/modules/facets_summary/src/Plugin/Block/FacetsSummaryBlock.php
+++ b/modules/facets_summary/src/Plugin/Block/FacetsSummaryBlock.php
@@ -96,6 +96,17 @@ class FacetsSummaryBlock extends BlockBase implements FacetsSummaryBlockInterfac
       ];
     }
 
+    /** @var \Drupal\views\Entity\View $view */
+    $view = $facets_summary->getFacetSource()->getView();
+
+    $build['#attached']['drupalSettings']['facets_views_ajax'] = [
+      'facets_summary_ajax' => [
+        'facets_summary_id' => $build['#attributes']['data-drupal-facets-summary-id'],
+        'view_id' => $view->id(),
+        'current_display_id' => $view->current_display
+      ]
+    ];
+
     return $build;
 
   }
diff --git a/src/Controller/FacetBlockAjaxController.php b/src/Controller/FacetBlockAjaxController.php
new file mode 100644
index 0000000..409d100
--- /dev/null
+++ b/src/Controller/FacetBlockAjaxController.php
@@ -0,0 +1,199 @@
+<?php
+
+namespace Drupal\facets\Controller;
+
+use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\Core\Ajax\InvokeCommand;
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Entity\EntityManager;
+use Drupal\Core\Logger\LoggerChannelFactoryInterface;
+use Drupal\Core\Path\CurrentPathStack;
+use Drupal\Core\PathProcessor\PathProcessorManager;
+use Drupal\Core\Render\Markup;
+use Drupal\Core\Render\RendererInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestStack;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+use Symfony\Component\Routing\RouterInterface;
+
+/**
+ * Defines a controller to load a view via AJAX.
+ */
+class FacetBlockAjaxController implements ContainerInjectionInterface {
+
+  /**
+   * The container
+   *
+   * @var \Symfony\Component\DependencyInjection\ContainerInterface
+   */
+  protected $container;
+
+  /**
+   * The entity storage for block.
+   *
+   * @var \Drupal\Core\Entity\EntityStorageInterface
+   */
+  protected $storage;
+
+  /**
+   * The renderer.
+   *
+   * @var \Drupal\Core\Render\RendererInterface
+   */
+  protected $renderer;
+
+  /**
+   * The current path.
+   *
+   * @var \Drupal\Core\Path\CurrentPathStack
+   */
+  protected $currentPath;
+
+  /**
+   * The dynamic router service.
+   *
+   * @var \Symfony\Component\Routing\Matcher\RequestMatcherInterface
+   */
+  protected $router;
+
+  /**
+   * The path processor service.
+   *
+   * @var \Drupal\Core\PathProcessor\InboundPathProcessorInterface
+   */
+  protected $pathProcessor;
+
+  /**
+   * The logger service.
+   *
+   * @var
+   */
+  protected $logger;
+
+  /**
+   * @var \Drupal\Core\Entity\EntityManager
+   */
+  protected $entityManager;
+
+
+  /**
+   * Constructs a FacetBlockAjaxController object.
+   *
+   * @param \Drupal\Core\Entity\EntityManager $entityManager
+   * @param \Drupal\Core\Render\RendererInterface $renderer
+   * @param \Drupal\Core\Path\CurrentPathStack $currentPath
+   * @param \Symfony\Component\Routing\RouterInterface $router
+   * @param \Drupal\Core\PathProcessor\PathProcessorManager $pathProcessor
+   * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger
+   */
+  public function __construct(EntityManager $entityManager, RendererInterface $renderer,
+                              CurrentPathStack $currentPath,
+                              RouterInterface $router,
+                              PathProcessorManager $pathProcessor,
+                              LoggerChannelFactoryInterface $logger) {
+    $this->entityManager = $entityManager;
+    $this->storage = $entityManager->getStorage('block');
+    $this->renderer = $renderer;
+    $this->currentPath = $currentPath;
+    $this->router = $router;
+    $this->pathProcessor = $pathProcessor;
+    $this->logger = $logger;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('entity.manager'),
+      $container->get('renderer'),
+      $container->get('path.current'),
+      $container->get('router'),
+      $container->get('path_processor_manager'),
+      $container->get('logger.factory')
+    );
+  }
+
+  /**
+   * Loads and renders the facet blocks via AJAX.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The current request object.
+   *
+   * @return \Drupal\Core\Ajax\AjaxResponse
+   *   The ajax response.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
+   *   Thrown when the view was not found.
+   */
+  public function ajaxFacetBlockView(Request $request) {
+    $response = new AjaxResponse();
+
+    // Rebuild the request and the current path, needed for facets.
+    $path = $request->request->get('facet_link');
+    $facets_blocks = $request->request->get('facets_blocks');
+
+    // We get duplicates in parameters.
+    $facets_blocks = array_unique($facets_blocks);
+
+    if (empty($path) || empty($facets_blocks)) {
+      throw new NotFoundHttpException();
+    }
+    $new_request = Request::create($path);
+    $requestStack = new RequestStack();
+    $processed = $this->pathProcessor->processInbound($path, $new_request);
+    if (empty($processed)) {
+      throw new NotFoundHttpException();
+    }
+    $this->currentPath->setPath($processed, $new_request);
+    $request->attributes->add($this->router->matchRequest($new_request));
+    $requestStack->push($new_request);
+    $container = \Drupal::getContainer();
+    $container->set('request_stack', $requestStack);
+    $activeFacet = $request->request->get('active_facet');
+
+    // Build the facets blocks found for the current request and update.
+    foreach ($facets_blocks as $facets_block) {
+      $facets_block_id = str_replace('_', '', $facets_block);
+      $block_entity = $this->storage->load($facets_block_id);
+      if ($block_entity) {
+        $block_view = $this->entityManager->getViewBuilder('block')
+          ->view($block_entity);
+        /** @var \Drupal\Core\Render\Markup $block_view */
+        $block_view = (string) $this->renderer->renderPlain($block_view);
+
+        // @Todo: Replace with ReplaceCommand once https://www.drupal.org/node/736066 gets fixed.
+        $data['selector'] = '.js-facet-dom-id-' . $facets_block;
+        $data['replaceWith'] = $block_view;
+        $response->addCommand(new InvokeCommand(NULL, 'replaceFacets', [$data]));
+      }
+    }
+
+    $response->addCommand(new InvokeCommand('[data-block-plugin-id="' . $activeFacet . '"]', 'addClass', ['facet-active']));
+
+    $update_summary_block = $request->request->get('update_summary_block');
+
+    if ($update_summary_block) {
+      $facet_summary_block_id = $request->request->get('facet_summary_block_id');
+      $facet_summary_wrapper_id = $request->request->get('facet_summary_wrapper_id');
+      $facet_summary_block_id = str_replace('-', '_', $facet_summary_block_id);
+      // Update filter summary block.
+      if ($facet_summary_block_id) {
+        $block_entity = $this->storage->load($facet_summary_block_id);
+        $block_view = $this->entityManager->getViewBuilder('block')
+          ->view($block_entity);
+        /** @var \Drupal\Core\Render\Markup $block_view */
+        $block_view = (string) $this->renderer->renderPlain($block_view);
+
+        // @Todo: Replace with ReplaceCommand once https://www.drupal.org/node/736066 gets fixed.
+        $data['selector'] = '[data-drupal-facets-summary-id=' . $facet_summary_wrapper_id . ']';
+        $data['replaceWith'] = $block_view;
+        $response->addCommand(new InvokeCommand(NULL, 'replaceFacets', [$data]));
+      }
+    }
+
+    return $response;
+  }
+
+}
diff --git a/src/FacetSource/FacetSourcePluginBase.php b/src/FacetSource/FacetSourcePluginBase.php
index 14317fb..9a470f8 100644
--- a/src/FacetSource/FacetSourcePluginBase.php
+++ b/src/FacetSource/FacetSourcePluginBase.php
@@ -119,6 +119,13 @@ abstract class FacetSourcePluginBase extends PluginBase implements FacetSourcePl
   /**
    * {@inheritdoc}
    */
+  public function buildFacet() {
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function getCount() {
     global $pager_total_items;
     // Exposing a global here. This is pretty ugly but the only way to get the
diff --git a/src/FacetSource/FacetSourcePluginInterface.php b/src/FacetSource/FacetSourcePluginInterface.php
index d88d3e5..00a129f 100644
--- a/src/FacetSource/FacetSourcePluginInterface.php
+++ b/src/FacetSource/FacetSourcePluginInterface.php
@@ -105,4 +105,13 @@ interface FacetSourcePluginInterface extends PluginFormInterface, DependentPlugi
    */
   public function getDataDefinition($field_name);
 
+  /**
+   * Builds and returns an extra renderable array for this facet block plugin.
+   *
+   * @return array
+   *   A renderable array representing the content of the block.
+   *
+   * @see Drupal\facets\Plugin\facets\facet_source\SearchApiDisplay
+   */
+  public function buildFacet();
 }
diff --git a/src/FacetSource/FacetSourcePluginInterface.php.orig b/src/FacetSource/FacetSourcePluginInterface.php.orig
new file mode 100644
index 0000000..d88d3e5
--- /dev/null
+++ b/src/FacetSource/FacetSourcePluginInterface.php.orig
@@ -0,0 +1,108 @@
+<?php
+
+namespace Drupal\facets\FacetSource;
+
+use Drupal\Component\Plugin\DependentPluginInterface;
+use Drupal\Core\Plugin\PluginFormInterface;
+use Drupal\facets\FacetInterface;
+
+/**
+ * Describes a source for facet items.
+ *
+ * A facet source is used to abstract the data source where facets can be added
+ * to. A good example of this is a Search API view. There are other possible
+ * facet data sources, these all implement the FacetSourcePluginInterface.
+ *
+ * @see plugin_api
+ */
+interface FacetSourcePluginInterface extends PluginFormInterface, DependentPluginInterface {
+
+  /**
+   * Fills the facet entities with results from the facet source.
+   *
+   * @param \Drupal\facets\FacetInterface[] $facets
+   *   The configured facets.
+   */
+  public function fillFacetsWithResults(array $facets);
+
+  /**
+   * Returns the allowed query types for a given facet for the facet source.
+   *
+   * @param \Drupal\facets\FacetInterface $facet
+   *   The facet we should get query types for.
+   *
+   * @return string[]
+   *   array of allowed query types
+   *
+   * @throws \Drupal\facets\Exception\Exception
+   *   An error when no query types are found.
+   */
+  public function getQueryTypesForFacet(FacetInterface $facet);
+
+  /**
+   * Returns the path of the facet source, used to build the facet url.
+   *
+   * @return string
+   *   The path.
+   */
+  public function getPath();
+
+  /**
+   * Returns the number of results that were found for this search.
+   *
+   * @return string
+   *   The path of the facet.
+   */
+  public function getCount();
+
+  /**
+   * Returns true if the Facet source is being rendered in the current request.
+   *
+   * This function will define if all facets for this facet source are shown
+   * when facet source visibility: "being rendered" is configured in the facet
+   * visibility settings.
+   *
+   * @return bool
+   *   True when the facet is rendered on the same page.
+   */
+  public function isRenderedInCurrentRequest();
+
+  /**
+   * Returns an array of fields that are defined on the facet source.
+   *
+   * This returns an array of fields that are defined on the source. This array
+   * is keyed by the field's machine name and has values of the field's label.
+   *
+   * @return array
+   *   An array of available fields.
+   */
+  public function getFields();
+
+  /**
+   * Sets the search keys, or query text, submitted by the user.
+   *
+   * @param string $keys
+   *   The search keys, or query text, submitted by the user.
+   *
+   * @return self
+   *   An instance of this class.
+   */
+  public function setSearchKeys($keys);
+
+  /**
+   * Returns the search keys, or query text, submitted by the user.
+   *
+   * @return string
+   *   The search keys, or query text, submitted by the user.
+   */
+  public function getSearchKeys();
+
+  /**
+   * Returns a single field's data definition from the facet source.
+   *
+   * @return \Drupal\Core\TypedData\DataDefinitionInterface
+   *   A typed data definition.
+   */
+  public function getDataDefinition($field_name);
+
+}
diff --git a/src/Plugin/Block/FacetBlock.php b/src/Plugin/Block/FacetBlock.php
index b58c54d..232dd83 100644
--- a/src/Plugin/Block/FacetBlock.php
+++ b/src/Plugin/Block/FacetBlock.php
@@ -86,11 +86,24 @@ class FacetBlock extends BlockBase implements ContainerFactoryPluginInterface {
     // Let the facet_manager build the facets.
     $build = $this->facetManager->build($facet);
 
-    // Add contextual links only when we have results.
     if (!empty($build)) {
+      // Add extra elements from facet source.
+      // For example, ajax scripts.
+      // @see Drupal\facets\Plugin\facets\facet_source\SearchApiDisplay
+      /** @var \Drupal\facets\FacetSource\FacetSourcePluginInterface $facet_source */
+      $facet_source = $facet->getFacetSource();
+      $build += $facet_source->buildFacet();
+
+      // Add contextual links only when we have results.
       $build['#contextual_links']['facets_facet'] = [
         'route_parameters' => ['facets_facet' => $facet->id()],
       ];
+
+      // Add ajax class.
+      if (!empty($build['#use_ajax'])) {
+        $build['#attributes']['class'][] = 'block-facets-ajax';
+        $build['#attributes']['class'][] = 'js-facet-dom-id-' . $facet_id;
+      }
     }
 
     return $build;
diff --git a/src/Plugin/Block/FacetBlock.php.orig b/src/Plugin/Block/FacetBlock.php.orig
new file mode 100644
index 0000000..b58c54d
--- /dev/null
+++ b/src/Plugin/Block/FacetBlock.php.orig
@@ -0,0 +1,147 @@
+<?php
+
+namespace Drupal\facets\Plugin\Block;
+
+use Drupal\Core\Block\BlockBase;
+use Drupal\Core\Entity\EntityStorageInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Plugin\PluginBase;
+use Drupal\facets\FacetManager\DefaultFacetManager;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Exposes a facet rendered as a block.
+ *
+ * @Block(
+ *   id = "facet_block",
+ *   deriver = "Drupal\facets\Plugin\Block\FacetBlockDeriver"
+ * )
+ */
+class FacetBlock extends BlockBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * The facet manager.
+   *
+   * @var \Drupal\facets\FacetManager\DefaultFacetManager
+   */
+  protected $facetManager;
+
+  /**
+   * The entity storage used for facets.
+   *
+   * @var \Drupal\Core\Entity\EntityStorageInterface
+   */
+  protected $facetStorage;
+
+  /**
+   * Construct a FacetBlock instance.
+   *
+   * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param string $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Drupal\facets\FacetManager\DefaultFacetManager $facet_manager
+   *   The facet manager.
+   * @param \Drupal\Core\Entity\EntityStorageInterface $facet_storage
+   *   The entity storage used for facets.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, DefaultFacetManager $facet_manager, EntityStorageInterface $facet_storage) {
+    $this->facetManager = $facet_manager;
+    $this->facetStorage = $facet_storage;
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('facets.manager'),
+      $container->get('entity_type.manager')->getStorage('facets_facet')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build() {
+    // The id saved in the configuration is in the format of
+    // base_plugin:facet_id. We're splitting that to get to the facet id.
+    $facet_mapping = $this->configuration['id'];
+    $facet_id = explode(PluginBase::DERIVATIVE_SEPARATOR, $facet_mapping)[1];
+
+    /** @var \Drupal\facets\FacetInterface $facet */
+    $facet = $this->facetStorage->load($facet_id);
+
+    // No need to build the facet if it does not need to be visible.
+    if ($facet->getOnlyVisibleWhenFacetSourceIsVisible() && !$facet->getFacetSource()->isRenderedInCurrentRequest()) {
+      return;
+    }
+
+    // Let the facet_manager build the facets.
+    $build = $this->facetManager->build($facet);
+
+    // Add contextual links only when we have results.
+    if (!empty($build)) {
+      $build['#contextual_links']['facets_facet'] = [
+        'route_parameters' => ['facets_facet' => $facet->id()],
+      ];
+    }
+
+    return $build;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheMaxAge() {
+    // A facet block cannot be cached, because it must always match the current
+    // search results, and Search API gets those search results from a data
+    // source that can be external to Drupal. Therefore it is impossible to
+    // guarantee that the search results are in sync with the data managed by
+    // Drupal. Consequently, it is not possible to cache the search results at
+    // all. If the search results cannot be cached, then neither can the facets,
+    // because they must always match.
+    // Fortunately, facet blocks are rendered using a lazy builder (like all
+    // blocks in Drupal), which means their rendering can be deferred (unlike
+    // the search results, which are the main content of the page, and deferring
+    // their rendering would mean sending an empty page to the user). This means
+    // that facet blocks can be rendered and sent *after* the initial page was
+    // loaded, by installing the BigPipe (big_pipe) module.
+    //
+    // When BigPipe is enabled, the search results will appear first, and then
+    // each facet block will appear one-by-one, in DOM order.
+    // See https://www.drupal.org/project/big_pipe.
+    //
+    // In a future version of Facet API, this could be refined, but due to the
+    // reliance on external data sources, it will be very difficult if not
+    // impossible to improve this significantly.
+    //
+    // Note: when using Drupal core's Search module instead of the contributed
+    // Search API module, the above limitations do not apply, but for now it is
+    // not considered worth the effort to optimize this just for Drupal core's
+    // Search.
+    return 0;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function calculateDependencies() {
+    // The ID saved in the configuration is of the format
+    // 'base_plugin:facet_id'. We're splitting that to get to the facet ID.
+    $facet_mapping = $this->getPluginId();
+    $facet_id = explode(PluginBase::DERIVATIVE_SEPARATOR, $facet_mapping)[1];
+
+    /** @var \Drupal\facets\FacetInterface $facet */
+    $facet = $this->facetStorage->load($facet_id);
+
+    return ['config' => [$facet->getConfigDependencyName()]];
+  }
+
+}
diff --git a/src/Plugin/facets/facet_source/SearchApiDisplay.php b/src/Plugin/facets/facet_source/SearchApiDisplay.php
index 028d386..5c3db30 100644
--- a/src/Plugin/facets/facet_source/SearchApiDisplay.php
+++ b/src/Plugin/facets/facet_source/SearchApiDisplay.php
@@ -372,4 +372,44 @@ class SearchApiDisplay extends FacetSourcePluginBase implements SearchApiFacetSo
     throw new Exception("Field with name {$field_name} does not have a definition");
   }
 
+  /**
+   * Return the Search API display source view, if any.
+   *
+   * @return \Drupal\views\Views
+   *   Current view or FALSE.
+   */
+  public function getView() {
+    $display_definition = $this->getDisplay()->getPluginDefinition();
+    if (isset($display_definition['view_id'])) {
+      $view = Views::getView($display_definition['view_id']);
+      $view->setDisplay($display_definition['view_display']);
+      return $view;
+    }
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildFacet() {
+    $build = parent::buildFacet();
+    /** @var \Drupal\views\ViewExecutable $view */
+    if ($view = $this->getView()) {
+      // Add JS for Views with Ajax Enabled.
+      if ($view->display_handler->ajaxEnabled()) {
+        $js_settings = [
+          'view_id' => $view->id(),
+          'current_display_id' => $view->current_display,
+          'view_base_path' => ltrim($view->getPath(), '/'),
+        ];
+        $build['#attached']['library'][] = 'facets/drupal.facets.views-ajax';
+        $build['#attached']['drupalSettings']['facets_views_ajax'] = [
+          $this->facet->id() => $js_settings,
+        ];
+        $build['#use_ajax'] = TRUE;
+      }
+    }
+    return $build;
+  }
+
 }
diff --git a/src/Plugin/facets/facet_source/SearchApiDisplay.php.orig b/src/Plugin/facets/facet_source/SearchApiDisplay.php.orig
new file mode 100644
index 0000000..028d386
--- /dev/null
+++ b/src/Plugin/facets/facet_source/SearchApiDisplay.php.orig
@@ -0,0 +1,375 @@
+<?php
+
+namespace Drupal\facets\Plugin\facets\facet_source;
+
+use Drupal\Component\Plugin\DependentPluginInterface;
+use Drupal\Core\Extension\ModuleHandler;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\facets\Exception\Exception;
+use Drupal\facets\Exception\InvalidQueryTypeException;
+use Drupal\facets\FacetInterface;
+use Drupal\facets\FacetSource\FacetSourcePluginBase;
+use Drupal\facets\FacetSource\SearchApiFacetSourceInterface;
+use Drupal\facets\QueryType\QueryTypePluginManager;
+use Drupal\search_api\Backend\BackendInterface;
+use Drupal\search_api\Display\DisplayPluginManager;
+use Drupal\search_api\FacetsQueryTypeMappingInterface;
+use Drupal\search_api\Query\ResultSetInterface;
+use Drupal\search_api\Utility\QueryHelper;
+use Drupal\views\Views;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Provides a facet source based on a Search API display.
+ *
+ * @FacetsFacetSource(
+ *   id = "search_api",
+ *   deriver = "Drupal\facets\Plugin\facets\facet_source\SearchApiDisplayDeriver"
+ * )
+ */
+class SearchApiDisplay extends FacetSourcePluginBase implements SearchApiFacetSourceInterface {
+
+  /**
+   * The search index the query should is executed on.
+   *
+   * @var \Drupal\search_api\IndexInterface
+   */
+  protected $index;
+
+  /**
+   * The display plugin manager.
+   *
+   * @var \Drupal\search_api\Display\DisplayPluginManager
+   */
+  protected $displayPluginManager;
+
+  /**
+   * The search result cache.
+   *
+   * @var \Drupal\search_api\Utility\QueryHelper
+   */
+  protected $searchApiQueryHelper;
+
+  /**
+   * The clone of the current request.
+   *
+   * @var \Symfony\Component\HttpFoundation\Request
+   */
+  protected $request;
+
+  /**
+   * The Drupal module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandler
+   */
+  protected $moduleHandler;
+
+  /**
+   * Constructs a SearchApiBaseFacetSource object.
+   *
+   * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Drupal\facets\QueryType\QueryTypePluginManager $query_type_plugin_manager
+   *   The query type plugin manager.
+   * @param \Drupal\search_api\Utility\QueryHelper $search_results_cache
+   *   The query type plugin manager.
+   * @param \Drupal\search_api\Display\DisplayPluginManager $display_plugin_manager
+   *   The display plugin manager.
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   A request object for the current request.
+   * @param \Drupal\Core\Extension\ModuleHandler $moduleHandler
+   *   Core's module handler class.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, QueryTypePluginManager $query_type_plugin_manager, QueryHelper $search_results_cache, DisplayPluginManager $display_plugin_manager, Request $request, ModuleHandler $moduleHandler) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition, $query_type_plugin_manager);
+
+    $this->searchApiQueryHelper = $search_results_cache;
+    $this->displayPluginManager = $display_plugin_manager;
+    $this->moduleHandler = $moduleHandler;
+    $this->request = clone $request;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    // If the Search API module is not enabled, we should just return an empty
+    // object. This allows us to have this class in the module without having a
+    // dependency on the Search API module.
+    if (!$container->get('module_handler')->moduleExists('search_api')) {
+      return new \stdClass();
+    }
+
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('plugin.manager.facets.query_type'),
+      $container->get('search_api.query_helper'),
+      $container->get('plugin.manager.search_api.display'),
+      $container->get('request_stack')->getMasterRequest(),
+      $container->get('module_handler')
+    );
+  }
+
+  /**
+   * Retrieves the Search API index for this facet source.
+   *
+   * @return \Drupal\search_api\IndexInterface
+   *   The search index.
+   */
+  public function getIndex() {
+    if ($this->index === NULL) {
+      $this->index = $this->getDisplay()->getIndex();
+    }
+
+    return $this->index;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPath() {
+    // The implementation in search api tells us that this is a base path only
+    // if a path is defined, and false if that isn't done. This means that we
+    // have to check for this + create our own uri if that's needed.
+    if ($this->getDisplay()->getPath()) {
+      return $this->getDisplay()->getPath();
+    }
+
+    return \Drupal::service('path.current')->getPath();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fillFacetsWithResults(array $facets) {
+    $search_id = $this->getDisplay()->getPluginId();
+
+    // Check if the results for this search id are already populated in the
+    // query helper. This is usually the case for views displays that are
+    // rendered on the same page, such as views_page.
+    $results = $this->searchApiQueryHelper->getResults($search_id);
+
+    // If there are no results, we can check the Search API Display plugin has
+    // configuration for views. If that configuration exists, we can execute
+    // that view and try to use it's results.
+    $display_definition = $this->getDisplay()->getPluginDefinition();
+    if ($results === NULL && isset($display_definition['view_id'])) {
+      $view = Views::getView($display_definition['view_id']);
+      $view->setDisplay($display_definition['view_display']);
+      $view->execute();
+      $results = $this->searchApiQueryHelper->getResults($search_id);
+    }
+
+    if (!$results instanceof ResultSetInterface) {
+      return;
+    }
+
+    // Get our facet data.
+    $facet_results = $results->getExtraData('search_api_facets');
+
+    // If no data is found in the 'search_api_facets' extra data, we can stop
+    // execution here.
+    if ($facet_results === []) {
+      return;
+    }
+
+    // Loop over each facet and execute the build method from the given
+    // query type.
+    foreach ($facets as $facet) {
+      $configuration = [
+        'query' => $results->getQuery(),
+        'facet' => $facet,
+        'results' => isset($facet_results[$facet->getFieldIdentifier()]) ? $facet_results[$facet->getFieldIdentifier()] : [],
+      ];
+
+      // Get the Facet Specific Query Type so we can process the results
+      // using the build() function of the query type.
+      $query_type = $this->queryTypePluginManager->createInstance($facet->getQueryType(), $configuration);
+      $query_type->build();
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isRenderedInCurrentRequest() {
+    return $this->getDisplay()->isRenderedInCurrentRequest();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+    $form['field_identifier'] = [
+      '#type' => 'select',
+      '#options' => $this->getFields(),
+      '#title' => $this->t('Field'),
+      '#description' => $this->t('The field from the selected facet source which contains the data to build a facet for.<br> The field types supported are <strong>boolean</strong>, <strong>date</strong>, <strong>decimal</strong>, <strong>integer</strong> and <strong>string</strong>.'),
+      '#required' => TRUE,
+      '#default_value' => $this->facet->getFieldIdentifier(),
+    ];
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFields() {
+    $indexed_fields = [];
+    $index = $this->getIndex();
+
+    $fields = $index->getFields();
+    $server = $index->getServerInstance();
+    $backend = $server->getBackend();
+
+    foreach ($fields as $field) {
+      $data_type_plugin_id = $field->getDataTypePlugin()->getPluginId();
+      $query_types = $this->getQueryTypesForDataType($backend, $data_type_plugin_id);
+      if (!empty($query_types)) {
+        $indexed_fields[$field->getFieldIdentifier()] = $field->getLabel() . ' (' . $field->getPropertyPath() . ')';
+      }
+    }
+
+    return $indexed_fields;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getQueryTypesForFacet(FacetInterface $facet) {
+    // Get our Facets Field Identifier, which is equal to the Search API Field
+    // identifier.
+    $field_id = $facet->getFieldIdentifier();
+    /** @var \Drupal\search_api\IndexInterface $index */
+    $index = $this->getIndex();
+    // Get the Search API Server.
+    $server = $index->getServerInstance();
+    // Get the Search API Backend.
+    $backend = $server->getBackend();
+
+    $fields = $index->getFields();
+    foreach ($fields as $field) {
+      if ($field->getFieldIdentifier() == $field_id) {
+        return $this->getQueryTypesForDataType($backend, $field->getType());
+      }
+    }
+
+    throw new InvalidQueryTypeException("No available query types were found for facet {$facet->getName()}");
+  }
+
+  /**
+   * Retrieves the query types for a specified data type.
+   *
+   * Backend plugins can use this method to override the default query types
+   * provided by the Search API with backend-specific ones that better use
+   * features of that backend.
+   *
+   * @param \Drupal\search_api\Backend\BackendInterface $backend
+   *   The backend that we want to get the query types for.
+   * @param string $data_type_plugin_id
+   *   The identifier of the data type.
+   *
+   * @return string[]
+   *   An associative array with the plugin IDs of allowed query types, keyed by
+   *   the generic name of the query_type.
+   *
+   * @see hook_facets_search_api_query_type_mapping_alter()
+   */
+  protected function getQueryTypesForDataType(BackendInterface $backend, $data_type_plugin_id) {
+    $query_types = [];
+    $query_types['string'] = 'search_api_string';
+
+    // Add additional query types for specific data types.
+    switch ($data_type_plugin_id) {
+      case 'date':
+        $query_types['date'] = 'search_api_date';
+        $query_types['range'] = 'search_api_range';
+        break;
+
+      case 'decimal':
+      case 'integer':
+        $query_types['numeric'] = 'search_api_granular';
+        $query_types['range'] = 'search_api_range';
+        break;
+
+    }
+
+    // Find out if the backend implemented the Interface to retrieve specific
+    // query types for the supported data_types.
+    if ($backend instanceof FacetsQueryTypeMappingInterface) {
+      $mapping = [
+        $data_type_plugin_id => &$query_types,
+      ];
+      $backend->alterFacetQueryTypeMapping($mapping);
+    }
+    // Add it to a variable so we can pass it by reference. Alter hook complains
+    // due to the property of the backend object is not passable by reference.
+    $backend_plugin_id = $backend->getPluginId();
+
+    // Let modules alter this mapping.
+    \Drupal::moduleHandler()
+      ->alter('facets_search_api_query_type_mapping', $backend_plugin_id, $query_types);
+
+    return $query_types;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function calculateDependencies() {
+    $display = $this->getDisplay();
+    if ($display instanceof DependentPluginInterface) {
+      return $display->calculateDependencies();
+    }
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDisplay() {
+    return $this->displayPluginManager
+      ->createInstance($this->pluginDefinition['display_id']);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getViewsDisplay() {
+    if (!$this->moduleHandler->moduleExists('views')) {
+      return NULL;
+    }
+
+    $search_api_display_definition = $this->getDisplay()->getPluginDefinition();
+    if (empty($search_api_display_definition['view_id'])) {
+      return NULL;
+    }
+
+    $view_id = $search_api_display_definition['view_id'];
+    $view_display = $search_api_display_definition['view_display'];
+
+    $view = Views::getView($view_id);
+    $view->setDisplay($view_display);
+    return $view;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDataDefinition($field_name) {
+    $field = $this->getIndex()->getField($field_name);
+    if ($field) {
+      return $field->getDataDefinition();
+    }
+    throw new Exception("Field with name {$field_name} does not have a definition");
+  }
+
+}
diff --git a/src/Widget/WidgetPluginBase.php b/src/Widget/WidgetPluginBase.php
index 68cf0a7..4f724a1 100644
--- a/src/Widget/WidgetPluginBase.php
+++ b/src/Widget/WidgetPluginBase.php
@@ -6,6 +6,7 @@ use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Link;
 use Drupal\Core\Plugin\PluginBase;
+use Drupal\Core\Url;
 use Drupal\facets\FacetInterface;
 use Drupal\facets\Result\Result;
 use Drupal\facets\Result\ResultInterface;
@@ -54,6 +55,18 @@ abstract class WidgetPluginBase extends PluginBase implements WidgetPluginInterf
         return $this->buildResultItem($result);
       }
       else {
+        $url = $result->getUrl();
+        $options = $url->getOptions();
+        $request = \Drupal::request();
+        $pathinfo = $request->getPathInfo();
+        $url_object = \Drupal::service('path.validator')
+          ->getUrlIfValid($pathinfo);
+        $route_params = $url_object->getRouteParameters();
+        $route_name = $url_object->getRouteName();
+        // Additional check needed if facets are tied up with a block on the page.
+        if ($result->getUrl()->getRouteName() === 'facets.block.ajax') {
+          $result->setUrl(new Url($route_name, $route_params, $options));
+        }
         return $this->buildListItems($facet, $result);
       }
     }, $facet->getResults());
