diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module
index 16949ffd9e..e1226ff312 100644
--- a/core/modules/comment/comment.module
+++ b/core/modules/comment/comment.module
@@ -236,6 +236,117 @@ function comment_entity_view(array &$build, EntityInterface $entity, EntityViewD
           'value' => $entity->url('canonical', $options),
         ];
       }
+      elseif ($view_mode == 'teaser') {
+        // Teaser view: display the number of comments that have been posted,
+        // or a link to add new comments if the user has permission, the node
+        // is open to new comments, and there currently are none.
+        if (user_access('access comments')) {
+          if (!empty($entity->get($field_name)->comment_count)) {
+            $links['comment-comments'] = array(
+              'title' => format_plural($entity->get($field_name)->comment_count, '1 comment', '@count comments'),
+              'attributes' => array('title' => t('Jump to the first comment of this posting.')),
+              'fragment' => 'comments',
+              'html' => TRUE,
+            ) + $entity->urlInfo()->toArray();
+            if (\Drupal::moduleHandler()->moduleExists('history')) {
+              $links['comment-new-comments'] = array(
+                'title' => '',
+                'href' => '',
+                'attributes' => array(
+                  'class' => 'hidden',
+                  'title' => t('Jump to the first new comment of this posting.'),
+                  'data-history-node-last-comment-timestamp' => $entity->get($field_name)->last_comment_timestamp,
+                  'data-history-node-field-name' => $field_name,
+                ),
+                'html' => TRUE,
+              );
+            }
+          }
+        }
+        // Provide a link to new comment form.
+        if ($commenting_status == CommentItemInterface::OPEN) {
+          $comment_form_location = $field_definition->getSetting('form_location');
+          if (user_access('post comments')) {
+            $links['comment-add'] = array(
+              'title' => t('Add new comment'),
+              'language' => $entity->language(),
+              'attributes' => array('title' => t('Add a new comment to this page.')),
+              'fragment' => 'comment-form',
+            );
+            if ($comment_form_location == COMMENT_FORM_SEPARATE_PAGE) {
+              $links['comment-add']['route_name'] = 'comment.reply';
+              $links['comment-add']['route_parameters'] = array(
+                'entity_type' => $entity->getEntityTypeId(),
+                'entity_id' => $entity->id(),
+                'field_name' => $field_name,
+              );
+            }
+            else {
+              $links['comment-add'] += $entity->urlInfo()->toArray();
+            }
+          }
+          elseif (\Drupal::currentUser()->isAnonymous()) {
+            $links['comment-forbidden'] = array(
+              'title' => \Drupal::service('comment.helper')->forbiddenMessage($entity, $field_name),
+              'html' => TRUE,
+            );
+          }
+        }
+      }
+      else {
+        // Node in other view modes: add a "post comment" link if the user is
+        // allowed to post comments and if this node is allowing new comments.
+        if ($commenting_status == CommentItemInterface::OPEN) {
+          $comment_form_location = $field_definition->getSetting('form_location');
+          if (user_access('post comments')) {
+            // Show the "post comment" link if the form is on another page, or
+            // if there are existing comments that the link will skip past.
+            if ($comment_form_location == COMMENT_FORM_SEPARATE_PAGE || (!empty($entity->get($field_name)->comment_count) && user_access('access comments'))) {
+              $links['comment-add'] = array(
+                'title' => t('Add new comment'),
+                'attributes' => array('title' => t('Share your thoughts and opinions related to this posting.')),
+                'fragment' => 'comment-form',
+              );
+              if ($comment_form_location == COMMENT_FORM_SEPARATE_PAGE) {
+                $links['comment-add']['route_name'] = 'comment.reply';
+                $links['comment-add']['route_parameters'] = array(
+                  'entity_type' => $entity->getEntityTypeId(),
+                  'entity_id' => $entity->id(),
+                  'field_name' => $field_name,
+                );
+              }
+              else {
+                $links['comment-add'] += $entity->urlInfo()->toArray();
+              }
+            }
+          }
+          elseif (\Drupal::currentUser()->isAnonymous()) {
+            $links['comment-forbidden'] = array(
+              'title' => \Drupal::service('comment.helper')->forbiddenMessage($entity, $field_name),
+              'html' => TRUE,
+            );
+          }
+        }
+      }
+    }
+
+    if (!empty($links)) {
+      $node_links['comment__' . $field_name] = array(
+        '#theme' => 'links__entity__comment__' . $field_name,
+        '#links' => $links,
+        '#attributes' => array('class' => array('links', 'inline')),
+      );
+      if ($view_mode == 'teaser' && \Drupal::moduleHandler()->moduleExists('history') && \Drupal::currentUser()->isAuthenticated()) {
+        $node_links['comment__' . $field_name]['#attached']['library'][] = 'comment/drupal.node-new-comments-link';
+
+        // Embed the metadata for the "X new comments" link (if any) on this node.
+        $node_links['comment__' . $field_name]['#post_render_cache']['history_attach_timestamp'] = array(
+          array('node_id' => $entity->id()),
+        );
+        $node_links['comment__' . $field_name]['#post_render_cache']['Drupal\comment\CommentViewBuilder::attachNewCommentsLinkMetadata'] = array(
+          array('entity_type' => $entity->getEntityTypeId(), 'entity_id' => $entity->id(), 'field_name' => $field_name),
+        );
+      }
     }
   }
 }
diff --git a/core/modules/comment/comment.services.yml b/core/modules/comment/comment.services.yml
index 46398a06a4..24388e2f2b 100644
--- a/core/modules/comment/comment.services.yml
+++ b/core/modules/comment/comment.services.yml
@@ -7,7 +7,11 @@ services:
 
   comment.manager:
     class: Drupal\comment\CommentManager
-    arguments: ['@entity.manager', '@config.factory', '@string_translation', '@url_generator', '@module_handler', '@current_user']
+    arguments: ['@entity.manager', '@module_handler', '@current_user']
+
+  comment.helper:
+    class: Drupal\comment\CommentHelper
+    arguments: ['@entity.manager', '@config.factory', '@string_translation', '@url_generator']
 
   comment.statistics:
     class: Drupal\comment\CommentStatistics
diff --git a/core/modules/comment/src/CommentHelper.php b/core/modules/comment/src/CommentHelper.php
new file mode 100644
index 0000000000..a7845e58d9
--- /dev/null
+++ b/core/modules/comment/src/CommentHelper.php
@@ -0,0 +1,124 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\comment\CommentHelper.
+ */
+
+namespace Drupal\comment;
+
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Routing\UrlGeneratorInterface;
+use Drupal\Core\StringTranslation\TranslationInterface;
+
+/**
+ * Comment helper contains functions to render comment fields.
+ */
+class CommentHelper implements CommentHelperInterface {
+
+  /**
+   * The entity manager service.
+   *
+   * @var \Drupal\Core\Entity\EntityManagerInterface
+   */
+  protected $entityManager;
+
+  /**
+   * Whether the DRUPAL_AUTHENTICATED_RID can post comments.
+   *
+   * @var bool
+   */
+  protected $authenticatedCanPostComments;
+
+  /**
+   * The user settings config object.
+   *
+   * @var \Drupal\Core\Config\Config
+   */
+  protected $userConfig;
+
+  /**
+   * The string translation service.
+   *
+   * @var \Drupal\Core\StringTranslation\TranslationInterface
+   */
+  protected $translationManager;
+
+  /**
+   * The url generator service.
+   *
+   * @var \Drupal\Core\Routing\UrlGeneratorInterface
+   */
+  protected $urlGenerator;
+
+  /**
+   * Construct the CommentManager object.
+   *
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
+   *   The entity manager service.
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   The config factory.
+   * @param \Drupal\Core\StringTranslation\TranslationInterface $translation_manager
+   *   The string translation service.
+   * @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
+   *   The url generator service.
+   */
+  public function __construct(EntityManagerInterface $entity_manager, ConfigFactoryInterface $config_factory, TranslationInterface $translation_manager, UrlGeneratorInterface $url_generator) {
+    $this->entityManager = $entity_manager;
+    $this->userConfig = $config_factory->get('user.settings');
+    $this->translationManager = $translation_manager;
+    $this->urlGenerator = $url_generator;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function forbiddenMessage(EntityInterface $entity, $field_name) {
+    if (!isset($this->authenticatedCanPostComments)) {
+      // We only output a link if we are certain that users will get the
+      // permission to post comments by logging in.
+      $this->authenticatedCanPostComments = $this->entityManager
+        ->getStorage('user_role')
+        ->load(DRUPAL_AUTHENTICATED_RID)
+        ->hasPermission('post comments');
+    }
+
+    if ($this->authenticatedCanPostComments) {
+      // We cannot use drupal_get_destination() because these links
+      // sometimes appear on /node and taxonomy listing pages.
+      if ($entity->get($field_name)->getFieldDefinition()->getSetting('form_location') == COMMENT_FORM_SEPARATE_PAGE) {
+        $destination = array('destination' => 'comment/reply/' . $entity->getEntityTypeId() . '/' . $entity->id() . '/' . $field_name . '#comment-form');
+      }
+      else {
+        $destination = array('destination' => $entity->getSystemPath() . '#comment-form');
+      }
+
+      if ($this->userConfig->get('register') != USER_REGISTER_ADMINISTRATORS_ONLY) {
+        // Users can register themselves.
+        return $this->t('<a href="@login">Log in</a> or <a href="@register">register</a> to post comments', array(
+          '@login' => $this->urlGenerator->generateFromRoute('user.login', array(), array('query' => $destination)),
+          '@register' => $this->urlGenerator->generateFromRoute('user.register', array(), array('query' => $destination)),
+        ));
+      }
+      else {
+        // Only admins can add new users, no public registration.
+        return $this->t('<a href="@login">Log in</a> to post comments', array(
+          '@login' => $this->urlGenerator->generateFromRoute('user.login', array(), array('query' => $destination)),
+        ));
+      }
+    }
+    return '';
+  }
+
+  /**
+   * Translates a string to the current language or to a given language.
+   *
+   * See the t() documentation for details.
+   */
+  protected function t($string, array $args = array(), array $options = array()) {
+    return $this->translationManager->translate($string, $args, $options);
+  }
+
+}
diff --git a/core/modules/comment/src/CommentHelperInterface.php b/core/modules/comment/src/CommentHelperInterface.php
new file mode 100644
index 0000000000..dd2d282323
--- /dev/null
+++ b/core/modules/comment/src/CommentHelperInterface.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\comment\CommentHelperInterface.
+ */
+
+namespace Drupal\comment;
+
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Comment helper contains functions to render comment fields.
+ */
+interface CommentHelperInterface {
+
+  /**
+   * Provides a message if posting comments is forbidden.
+   *
+   * If authenticated users can post comments, a message is returned that
+   * prompts the anonymous user to log in (or register, if applicable) that
+   * redirects to entity comment form. Otherwise, no message is returned.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity to which comments are attached to.
+   * @param string $field_name
+   *   The field name on the entity to which comments are attached to.
+   *
+   * @return string
+   *   HTML for a "you can't post comments" notice.
+   */
+  public function forbiddenMessage(EntityInterface $entity, $field_name);
+
+}
diff --git a/core/modules/comment/src/CommentManager.php b/core/modules/comment/src/CommentManager.php
index 80cc427e34..e59167c915 100644
--- a/core/modules/comment/src/CommentManager.php
+++ b/core/modules/comment/src/CommentManager.php
@@ -2,13 +2,10 @@
 
 namespace Drupal\comment;
 
-use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
 use Drupal\Core\Config\ConfigFactoryInterface;
-use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Entity\FieldableEntityInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
-use Drupal\Core\Routing\UrlGeneratorInterface;
 use Drupal\Core\Routing\UrlGeneratorTrait;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
@@ -32,20 +29,6 @@ class CommentManager implements CommentManagerInterface {
   protected $entityManager;
 
   /**
-   * Whether the \Drupal\user\RoleInterface::AUTHENTICATED_ID can post comments.
-   *
-   * @var bool
-   */
-  protected $authenticatedCanPostComments;
-
-  /**
-   * The user settings config object.
-   *
-   * @var \Drupal\Core\Config\Config
-   */
-  protected $userConfig;
-
-  /**
    * The module handler service.
    *
    * @var \Drupal\Core\Extension\ModuleHandlerInterface
@@ -64,22 +47,16 @@ class CommentManager implements CommentManagerInterface {
    *
    * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
    *   The entity manager service.
-   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
-   *   The config factory.
    * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
    *   The string translation service.
-   * @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
-   *   The url generator service.
    *  @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The module handler service.
    * @param \Drupal\Core\Session\AccountInterface $current_user
    *   The current user.
    */
-  public function __construct(EntityManagerInterface $entity_manager, ConfigFactoryInterface $config_factory, TranslationInterface $string_translation, UrlGeneratorInterface $url_generator, ModuleHandlerInterface $module_handler, AccountInterface $current_user) {
+  public function __construct(EntityManagerInterface $entity_manager, TranslationInterface $string_translation, ModuleHandlerInterface $module_handler, AccountInterface $current_user) {
     $this->entityManager = $entity_manager;
-    $this->userConfig = $config_factory->get('user.settings');
     $this->stringTranslation = $string_translation;
-    $this->urlGenerator = $url_generator;
     $this->moduleHandler = $module_handler;
     $this->currentUser = $current_user;
   }
@@ -132,51 +109,6 @@ public function addBodyField($comment_type_id) {
   /**
    * {@inheritdoc}
    */
-  public function forbiddenMessage(EntityInterface $entity, $field_name) {
-    if (!isset($this->authenticatedCanPostComments)) {
-      // We only output a link if we are certain that users will get the
-      // permission to post comments by logging in.
-      $this->authenticatedCanPostComments = $this->entityManager
-        ->getStorage('user_role')
-        ->load(RoleInterface::AUTHENTICATED_ID)
-        ->hasPermission('post comments');
-    }
-
-    if ($this->authenticatedCanPostComments) {
-      // We cannot use the redirect.destination service here because these links
-      // sometimes appear on /node and taxonomy listing pages.
-      if ($entity->get($field_name)->getFieldDefinition()->getSetting('form_location') == CommentItemInterface::FORM_SEPARATE_PAGE) {
-        $comment_reply_parameters = [
-          'entity_type' => $entity->getEntityTypeId(),
-          'entity' => $entity->id(),
-          'field_name' => $field_name,
-        ];
-        $destination = ['destination' => $this->url('comment.reply', $comment_reply_parameters, ['fragment' => 'comment-form'])];
-      }
-      else {
-        $destination = ['destination' => $entity->url('canonical', ['fragment' => 'comment-form'])];
-      }
-
-      if ($this->userConfig->get('register') != USER_REGISTER_ADMINISTRATORS_ONLY) {
-        // Users can register themselves.
-        return $this->t('<a href=":login">Log in</a> or <a href=":register">register</a> to post comments', [
-          ':login' => $this->urlGenerator->generateFromRoute('user.login', [], ['query' => $destination]),
-          ':register' => $this->urlGenerator->generateFromRoute('user.register', [], ['query' => $destination]),
-        ]);
-      }
-      else {
-        // Only admins can add new users, no public registration.
-        return $this->t('<a href=":login">Log in</a> to post comments', [
-          ':login' => $this->urlGenerator->generateFromRoute('user.login', [], ['query' => $destination]),
-        ]);
-      }
-    }
-    return '';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function getCountNewComments(EntityInterface $entity, $field_name = NULL, $timestamp = 0) {
     // @todo Replace module handler with optional history service injection
     //   after https://www.drupal.org/node/2081585.
@@ -216,4 +148,11 @@ public function getCountNewComments(EntityInterface $entity, $field_name = NULL,
     return FALSE;
   }
 
+  public function getFieldUIPageTitle($commented_entity_type, $field_name) {
+    $field_info = $this->getFields($commented_entity_type);
+    $sample_bundle = reset($field_info[$field_name]['bundles']);
+    $sample_definition = $this->entityManager->getFieldDefinitions($commented_entity_type, $sample_bundle)[$field_name];
+    return String::checkPlain($sample_definition->getLabel());
+  }
+
 }
diff --git a/core/modules/comment/src/CommentViewBuilder.php b/core/modules/comment/src/CommentViewBuilder.php
index 51a9185853..4073e155e0 100644
--- a/core/modules/comment/src/CommentViewBuilder.php
+++ b/core/modules/comment/src/CommentViewBuilder.php
@@ -160,6 +160,107 @@ public function buildComponents(array &$build, array $entities, array $displays,
   }
 
   /**
+   * #post_render_cache callback; replaces the placeholder with comment links.
+   *
+   * Renders the links on a comment.
+   *
+   * @param array $element
+   *   The renderable array that contains the to be replaced placeholder.
+   * @param array $context
+   *   An array with the following keys:
+   *   - comment_entity_id: a comment entity ID
+   *   - view_mode: the view mode in which the comment entity is being viewed
+   *   - langcode: in which language the comment entity is being viewed
+   *   - commented_entity_type: the entity type to which the comment is attached
+   *   - commented_entity_id: the entity ID to which the comment is attached
+   *   - in_preview: whether the comment is currently being previewed
+   *
+   * @return array
+   *   A renderable array representing the comment links.
+   */
+  public static function renderLinks(array $element, array $context) {
+    $callback = '\Drupal\comment\CommentViewBuilder::renderLinks';
+    $placeholder = drupal_render_cache_generate_placeholder($callback, $context, $context['token']);
+    $links = array(
+      '#theme' => 'links__comment',
+      '#pre_render' => array('drupal_pre_render_links'),
+      '#attributes' => array('class' => array('links', 'inline')),
+    );
+
+    if (!$context['in_preview']) {
+      $entity = entity_load('comment', $context['comment_entity_id']);
+      $commented_entity = entity_load($context['commented_entity_type'], $context['commented_entity_id']);
+
+      $links['comment'] = self::buildLinks($entity, $commented_entity);
+
+      // Allow other modules to alter the comment links.
+      $hook_context = array(
+        'view_mode' => $context['view_mode'],
+        'langcode' => $context['langcode'],
+        'commented_entity' => $commented_entity
+      );
+      \Drupal::moduleHandler()->alter('comment_links', $links, $entity, $hook_context);
+    }
+    $markup = drupal_render($links);
+    $element['#markup'] = str_replace($placeholder, $markup, $element['#markup']);
+
+    return $element;
+  }
+
+  /**
+   * Build the default links (reply, edit, delete …) for a comment.
+   *
+   * @param \Drupal\comment\CommentInterface $entity
+   *   The comment object.
+   * @param \Drupal\Core\Entity\EntityInterface $commented_entity
+   *   The entity to which the comment is attached.
+   *
+   * @return array
+   *   An array that can be processed by drupal_pre_render_links().
+   */
+  protected static function buildLinks(CommentInterface $entity, EntityInterface $commented_entity) {
+    $links = array();
+    $status = $commented_entity->get($entity->getFieldName())->status;
+
+    if ($status == CommentItemInterface::OPEN) {
+      if ($entity->access('delete')) {
+        $links['comment-delete'] = array(
+          'title' => t('Delete'),
+          'href' => "comment/{$entity->id()}/delete",
+          'html' => TRUE,
+        );
+      }
+
+      if ($entity->access('update')) {
+        $links['comment-edit'] = array(
+          'title' => t('Edit'),
+          'href' => "comment/{$entity->id()}/edit",
+          'html' => TRUE,
+        );
+      }
+      if ($entity->access('create')) {
+        $links['comment-reply'] = array(
+          'title' => t('Reply'),
+          'href' => "comment/reply/{$entity->getCommentedEntityTypeId()}/{$entity->getCommentedEntityId()}/{$entity->getFieldName()}/{$entity->id()}",
+          'html' => TRUE,
+        );
+      }
+      if (!$entity->isPublished() && $entity->access('approve')) {
+        $links['comment-approve'] = array(
+          'title' => t('Approve'),
+          'route_name' => 'comment.approve',
+          'route_parameters' => array('comment' => $entity->id()),
+          'html' => TRUE,
+        );
+      }
+      if (empty($links) && \Drupal::currentUser()->isAnonymous()) {
+        $links['comment-forbidden']['title'] = \Drupal::service('comment.helper')->forbiddenMessage($commented_entity, $entity->getFieldName());
+        $links['comment-forbidden']['html'] = TRUE;
+      }
+    }
+  }
+
+  /**
    * {@inheritdoc}
    */
   protected function alterBuild(array &$build, EntityInterface $comment, EntityViewDisplayInterface $display, $view_mode) {
diff --git a/core/modules/comment/src/Tests/CommentLinksTest.php b/core/modules/comment/src/Tests/CommentLinksTest.php
new file mode 100644
index 0000000000..e69de29bb2
