diff --git a/media_entity_twitter.module b/media_entity_twitter.module
index 363d96b..980f0f6 100644
--- a/media_entity_twitter.module
+++ b/media_entity_twitter.module
@@ -13,5 +13,37 @@ function media_entity_twitter_theme() {
     'media_entity_twitter_tweet' => [
       'variables' => ['path' => NULL, 'attributes' => []],
     ],
+    'media_entity_twitter_tweet_thumbnail' => [
+      'variables' => [
+        'content' => '',
+        'author' => '',
+        'avatar' => NULL,
+      ],
+    ],
   ];
 }
+
+/**
+ * Preprocess function for media_entity_twitter_tweet_thumbnail theme hook.
+ *
+ * @param array $variables
+ *   Variables to be injected into the template.
+ */
+function media_entity_twitter_preprocess_media_entity_twitter_tweet_thumbnail(array &$variables) {
+  // If the avatar exists, load it directly into memory and base64 encode it.
+  // For security reasons, browsers don't always allow external images xlinked
+  // in an SVG to be displayed when the SVG is being embedded via an <img> tag.
+  // The workaround is to embed the image directly into the SVG as a base64
+  // encoded string.
+  if ($variables['avatar']) {
+    $data = file_get_contents($variables['avatar']);
+
+    if ($data) {
+      $extension = pathinfo($variables['avatar'], PATHINFO_EXTENSION);
+      $variables['avatar'] = 'data:image/' . strtolower($extension) . ';base64,' . base64_encode($data);
+    }
+    else {
+      unset($variables['avatar']);
+    }
+  }
+}
diff --git a/src/Plugin/MediaEntity/Type/Twitter.php b/src/Plugin/MediaEntity/Type/Twitter.php
index 18fa450..fb731ae 100644
--- a/src/Plugin/MediaEntity/Type/Twitter.php
+++ b/src/Plugin/MediaEntity/Type/Twitter.php
@@ -6,6 +6,7 @@ use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Entity\EntityFieldManagerInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\RendererInterface;
 use Drupal\media_entity\MediaInterface;
 use Drupal\media_entity\MediaTypeBase;
 use Drupal\media_entity\MediaTypeException;
@@ -31,6 +32,13 @@ class Twitter extends MediaTypeBase {
   protected $configFactory;
 
   /**
+   * The renderer.
+   *
+   * @var \Drupal\Core\Render\RendererInterface
+   */
+  protected $renderer;
+
+  /**
    * {@inheritdoc}
    */
   public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
@@ -40,7 +48,8 @@ class Twitter extends MediaTypeBase {
       $plugin_definition,
       $container->get('entity_type.manager'),
       $container->get('entity_field.manager'),
-      $container->get('config.factory')
+      $container->get('config.factory'),
+      $container->get('renderer')
     );
   }
 
@@ -68,10 +77,13 @@ class Twitter extends MediaTypeBase {
    *   Entity field manager service.
    * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
    *   Config factory service.
+   * @param \Drupal\Core\Render\RendererInterface $renderer
+   *   The renderer.
    */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, ConfigFactoryInterface $config_factory) {
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, ConfigFactoryInterface $config_factory, RendererInterface $renderer) {
     parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager, $entity_field_manager, $config_factory->get('media_entity.settings'));
     $this->configFactory = $config_factory;
+    $this->renderer = $renderer;
   }
 
   /**
@@ -138,21 +150,23 @@ class Twitter extends MediaTypeBase {
           return FALSE;
 
         case 'image_local':
-          if (isset($tweet['extended_entities']['media'][0]['media_url'])) {
-            $local_uri = $this->configFactory->get('media_entity_twitter.settings')->get('local_images') . '/' . $matches['id'] . '.' . pathinfo($tweet['extended_entities']['media'][0]['media_url'], PATHINFO_EXTENSION);
+          $local_uri = $this->getField($media, 'image_local_uri');
 
-            if (!file_exists($local_uri)) {
-              file_prepare_directory($this->configFactory->get('media_entity_twitter.settings')->get('local_images'), FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
-              file_unmanaged_save_data($tweet['extended_entities']['media'][0]['media_url'], $local_uri, FILE_EXISTS_REPLACE);
+          if ($local_uri) {
+            if (file_exists($local_uri)) {
+              return $local_uri;
+            }
+            else {
+              $image_url = $this->getField($media, 'image');
+              return file_unmanaged_save_data($image_url, $local_uri, FILE_EXISTS_REPLACE);
             }
-
-            return $local_uri;
           }
           return FALSE;
 
         case 'image_local_uri':
-          if (isset($tweet['extended_entities']['media'][0]['media_url'])) {
-            return $this->configFactory->get('media_entity_twitter.settings')->get('local_images') . '/' . $matches['id'] . '.' . pathinfo($tweet['extended_entities']['media'][0]['media_url'], PATHINFO_EXTENSION);
+          $image_url = $this->getField($media, 'image');
+          if ($image_url) {
+            return $this->getLocalImageUri($matches['id'], $image_url);
           }
           return FALSE;
 
@@ -280,6 +294,40 @@ class Twitter extends MediaTypeBase {
   }
 
   /**
+   * Computes the destination URI for a tweet image.
+   *
+   * @param mixed $id
+   *   The tweet ID.
+   * @param string|null $media_url
+   *   The URL of the media (i.e., photo, video, etc.) associated with the
+   *   tweet.
+   *
+   * @return string
+   *   The desired local URI.
+   */
+  protected function getLocalImageUri($id, $media_url = NULL) {
+    $directory = $this->configFactory
+      ->get('media_entity_twitter.settings')
+      ->get('local_images');
+
+    // Ensure the destination directory is writable. (TODO: Throw an exception
+    // or otherwise take action if it isn't.)
+    file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
+
+    $local_uri = $directory . '/' . $id . '.';
+    if ($media_url) {
+      $local_uri .= pathinfo($media_url, PATHINFO_EXTENSION);
+    }
+    else {
+      // If there is no media associated with the tweet, we will generate an
+      // SVG thumbnail.
+      $local_uri .= 'svg';
+    }
+
+    return $local_uri;
+  }
+
+  /**
    * {@inheritdoc}
    */
   public function getDefaultThumbnail() {
@@ -290,11 +338,30 @@ class Twitter extends MediaTypeBase {
    * {@inheritdoc}
    */
   public function thumbnail(MediaInterface $media) {
+    // If there's a local image, use that.
     if ($local_image = $this->getField($media, 'image_local')) {
       return $local_image;
     }
 
-    return $this->getDefaultThumbnail();
+    // We may need to generate a thumbnail.
+    $id = $this->getField($media, 'id');
+    $thumbnail_uri = $this->getLocalImageUri($id);
+
+    // If we already have a generated thumbnail, use that.
+    if (file_exists($thumbnail_uri)) {
+      return $thumbnail_uri;
+    }
+
+    // Render the thumbnail SVG using the theme system.
+    $thumbnail = [
+      '#theme' => 'media_entity_twitter_tweet_thumbnail',
+      '#content' => $this->getField($media, 'content'),
+      '#author' => $this->getField($media, 'user'),
+      '#avatar' => $this->getField($media, 'profile_image_url_https'),
+    ];
+    $svg = $this->renderer->renderRoot($thumbnail);
+
+    return file_unmanaged_save_data($svg, $thumbnail_uri, FILE_EXISTS_ERROR) ?: $this->getDefaultThumbnail();
   }
 
   /**
@@ -376,7 +443,7 @@ class Twitter extends MediaTypeBase {
         return Json::decode($result);
       }
       else {
-        throw new MediaTypeException(NULL, 'The tweet could not be retrived.');
+        throw new MediaTypeException(NULL, 'The tweet could not be retrieved.');
       }
     }
     else {
diff --git a/templates/media-entity-twitter-tweet-thumbnail.html.twig b/templates/media-entity-twitter-tweet-thumbnail.html.twig
new file mode 100644
index 0000000..7b3cd98
--- /dev/null
+++ b/templates/media-entity-twitter-tweet-thumbnail.html.twig
@@ -0,0 +1,19 @@
+<svg version="1.1" width="500" height="500" viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <rect width="100%" height="100%" fill="none" stroke="grey" rx="5%" ry="5%" />
+
+    {% if avatar %}
+    <image xlink:href="{{ avatar }}" width="25%" height="25%" x="5%" y="5%" />
+    {% endif %}
+
+    {% if author %}
+    <text font-family="Arial" fill="grey" font-size="32" x="32%" y="20%">{{ author }}</text>
+    {% endif %}
+
+    {% if content %}
+    <foreignObject x="5%" y="35%" width="90%" height="60%">
+        <body xmlns="http://www.w3.org/1999/xhtml">
+        <p style="font-family: Arial; font-size: 200%; line-height: 150%;">{{ content }}</p>
+        </body>
+    </foreignObject>
+    {% endif %}
+</svg>
