diff --git a/app/adapters/page.js b/app/adapters/page.js
index 7e1e34e95f9b4c877b7c29e3dbbcfe5a388378f5..686a2be58d7e7abbe8e7811ca247afb1b396feb9 100644
--- a/app/adapters/page.js
+++ b/app/adapters/page.js
@@ -26,17 +26,26 @@ export default class PageAdapter extends ApplicationAdapter {
 
   async loadImage(page, accept='image/jpeg') {
     /*
-    * Requests binary image/jpeg from backend and sets `page.url` attribute
-    * to the `URL` pointing to newly retrieved binary image.
+    * Requests page's image from backend
+
+      It can request image/jpeg or image/svg+xml. In case it asks
+      for image/svg+xml media type and svg image is not available,
+      server will return jpeg instead.
+      Server will return 404 only in case neither svg nor jpeg images
+      are available.
     */
     let response,
       image_blob,
       image_object_url;
 
     response = await this.getImage(page.id, accept);
-    image_blob = await response.blob();
-    image_object_url = URL.createObjectURL(image_blob);
-    page.url = image_object_url;
+    if (response.headers.get('content-type') == 'image/svg+xml') {
+      page.svg_image = await response.text();
+    } else {
+      image_blob = await response.blob();
+      image_object_url = URL.createObjectURL(image_blob);
+      page.url = image_object_url;
+    }
 
     return page;
   }
diff --git a/app/components/viewer/index.hbs b/app/components/viewer/index.hbs
index 779ea06dce569350c4120b7df28145c10ba817e9..2b58a4e961f20b166b5d2535b37673ae676fb510 100644
--- a/app/components/viewer/index.hbs
+++ b/app/components/viewer/index.hbs
@@ -1,10 +1,10 @@
 <div class="panel viewer col m-2 p-2">
   <div class="d-flex justify-content-between">
     <Viewer::ActionButtons
-      @document_versions={{@document_versions}}
-      @isLocked={{@isLocked}}
-      @ocrStatus={{@ocrStatus}}
-      @onRunOCR={{@onRunOCR}} />
+      @document_versions={{this.document_versions}}
+      @isLocked={{this.isLocked}}
+      @ocrStatus={{this.ocrStatus}}
+      @onRunOCR={{this.onRunOCR}} />
   </div>
 
   <Breadcrumb
@@ -13,7 +13,7 @@
     @hint={{@hint}} />
 
   <div class="d-flex">
-    <Viewer::Thumbnails @pages={{@pages}} />
-    <Viewer::Pages @pages={{@pages}} />
+    <Viewer::Thumbnails @pages={{this.pages}} />
+    <Viewer::Pages @pages={{this.pages}} />
   </div>
 </div>
diff --git a/app/components/viewer/index.js b/app/components/viewer/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..6da9cf5d85b0c9c5d1a5f43f46ea6dfe7479b015
--- /dev/null
+++ b/app/components/viewer/index.js
@@ -0,0 +1,168 @@
+import Component from '@glimmer/component';
+import { tracked } from '@glimmer/tracking';
+import { inject as service } from '@ember/service';
+import { action } from '@ember/object';
+import { A } from '@ember/array';
+
+
+export default class ViewerComponent extends Component {
+  /*
+  Document Viewer Component
+
+  Arguments:
+    @doc - `models.document` instance - the backend model document currently
+      shown to the user by this component
+    @pages - an array of `models.page` instances. Besides, each page
+    instance has either `url` or `svg_image` attribute.
+  */
+
+  @service websockets;
+  @service store;
+  @service requests;
+
+  @tracked ocr_status = null;
+  @tracked is_locked = false;
+
+  @tracked _document_versions = A([]);
+  @tracked __document_versions__;
+
+  @tracked _pages = A([]);
+  @tracked __pages__;
+
+  constructor(owner, args) {
+    super(owner, args);
+
+    this.websockets.addHandler(this.messageHandler, this);
+  }
+
+  messageHandler(message) {
+    // console.log(message);
+    if (message.document_id != this.args.doc.id) {
+      // this websocket message was not addressed to currently
+      // viewed document. Just skip it.
+      return;
+    }
+
+    // message addressed to currently viewed document
+    switch (message.type) {
+      case 'ocrdocumenttask.taskreceived':
+        this.ocr_status = 'received';
+        break;
+      case 'ocrdocumenttask.taskstarted':
+        this.ocr_status = 'started';
+        break;
+      case 'ocrdocumenttask.tasksucceeded':
+        this.ocr_status = 'succeeded';
+        this.is_locked = false;
+        this.update_document();
+        break;
+      case 'ocrdocumenttask.taskfailed':
+        this.ocr_status = 'failed';
+        break;
+      }  // end of switch
+  }
+
+  update_document() {
+    /*
+     Pulls latest document version + document version's pages
+     from server side.
+     */
+    let last_version,
+      page_adapter,
+      that = this;
+
+    page_adapter = this.store.adapterFor('page');
+
+    this.store.findRecord(
+      'document',
+      this.args.doc.id,
+      { reload: true }
+    ).then((doc) => {
+      last_version = doc.last_version;
+      that._document_versions.push(last_version);
+      that.__document_versions__ = last_version;
+
+      page_adapter.loadImages(last_version.pages, 'image/svg+xml').then(
+        (pages) => {
+          that._pages = pages;
+          that.__pages__ = pages;
+      });
+    });
+  }
+
+  @action
+  onRunOCR() {
+    this.is_locked = true;
+    this.requests.runOCR({
+      doc_id: this.args.doc.id,
+      lang: 'deu'
+    });
+  }
+
+  get versions() {
+    /* Just a shortcut */
+    return this.args.doc.versions;
+  }
+
+  get document_versions() {
+    /**
+    Returns document versions received via this.args.doc.versions + additional
+    versions created via OCRing.
+
+    The point here is to update versions dropdown with newly
+    created versions (when user clicks run OCR).
+    */
+    if (this.__document_versions__) {
+      // workaround for tracking changes in array
+    }
+
+    if (this._document_versions.length > 0) {
+      // include newly OCRed versions as well
+      return this.versions.concat(
+        this._document_versions
+      );
+    }
+
+    return this.versions;
+  }
+
+  get pages() {
+
+    if (this.__pages__) {
+      // workaround for tracking changes in array
+    }
+
+    if (this._pages.length > 0) {
+      // If newer version of the pages is available
+      // (e.g. document was OCRed) then just use it
+      return this._pages;
+    }
+
+    // Initial version of the pages
+    return this.args.pages;
+  }
+
+
+  get ocrStatus() {
+    /*
+    ocr status as received from:
+
+    1. websockets' messages for current document model
+    2. model itself
+
+    The notifications received via websockets's messages (1.) have priority
+    over current document model (2.)
+
+    Initially `this.ocr_status` will be `null` which will result in displaying
+    the actual ocr status from `this.args.doc.ocr_status`. If user chooses
+    to run OCR, the controller `this.ocr_status` will be updated in realtime
+    to non-null value (via websockets) and accordingly `this.ocr_status` will
+    take over in terms of priority.
+    */
+    return this.ocr_status || this.args.doc.ocr_status;
+  }
+
+  get isLocked() {
+    return this.is_locked;
+  }
+}
\ No newline at end of file
diff --git a/app/components/viewer/page.hbs b/app/components/viewer/page.hbs
index a45c717ec2e3e36447240a966b7b91021bcbef57..6c432f6e412dc877a07421cf36617667578ce4e7 100644
--- a/app/components/viewer/page.hbs
+++ b/app/components/viewer/page.hbs
@@ -1,5 +1,9 @@
 <div class="page d-flex flex-column align-items-center">
-  <img src="{{@page.url}}" />
+  {{#if @page.svg_image}}
+    {{{@page.svg_image}}}
+  {{else}}
+    <img src="{{@page.url}}" />
+  {{/if}}
   <div class="number fs-3 m-3">
     {{@page.number}}
   </div>
diff --git a/app/components/viewer/thumbnail.hbs b/app/components/viewer/thumbnail.hbs
index accedddff942de211be11958d65fb30cbc5812dd..293513a05d4a69d68ca29bb472c6c396aff81b34 100644
--- a/app/components/viewer/thumbnail.hbs
+++ b/app/components/viewer/thumbnail.hbs
@@ -1,4 +1,10 @@
 <div class="thumbnail d-flex flex-column align-items-center">
-  <img src="{{@page.url}}" />
-  {{@page.number}}
+  {{#if @page.svg_image}}
+    {{{@page.svg_image}}}
+  {{else}}
+    <img src="{{@page.url}}" />
+  {{/if}}
+  <div class="number fs-3 m-3">
+    {{@page.number}}
+  </div>
 </div>
\ No newline at end of file
diff --git a/app/controllers/authenticated/document.js b/app/controllers/authenticated/document.js
index 00571d1ce9097ee55595ef23e1e0b38625e8c8b6..ea57177d4275d1732f22b8b70225e674a8efcd37 100644
--- a/app/controllers/authenticated/document.js
+++ b/app/controllers/authenticated/document.js
@@ -2,90 +2,17 @@ import Controller from '@ember/controller';
 import { action } from '@ember/object';
 import { tracked } from "@glimmer/tracking";
 import { inject as service } from '@ember/service';
-import { A } from '@ember/array';
 
 
 export default class ViewerController extends Controller {
 
   @service currentUser;
-  @service requests;
-  @service websockets;
 
   @tracked extranode_id = null;
   @tracked extradoc_id = null;
-  @tracked is_locked = false;
-  @tracked ocr_status = null;
-
-  @tracked _document_versions = A([]);
-  @tracked __document_versions__;
-
-  @tracked _pages = A([]);
-  @tracked __pages__;
 
   queryParams = ['extranode_id', 'extradoc_id']
 
-
-  constructor(owner, args) {
-    super(owner, args);
-
-    this.websockets.addHandler(this.messageHandler, this);
-  }
-
-  update_document() {
-    /*
-     Pulls latest document version + document version's pages
-     from server side.
-     */
-    let last_version,
-      page_adapter,
-      that = this;
-
-    page_adapter = this.store.adapterFor('page');
-
-    this.store.findRecord(
-      'document',
-      this.model.doc.id,
-      { reload: true }
-    ).then((doc) => {
-      last_version = doc.last_version;
-      that._document_versions.push(last_version);
-      that.__document_versions__ = last_version;
-
-      page_adapter.loadImages(last_version.pages, 'image/svg+xml').then(
-        (pages) => {
-          that._pages = pages;
-          that.__pages__ = pages;
-      });
-    });
-  }
-
-  messageHandler(message) {
-    // console.log(message);
-    if (message.document_id != this.model.doc.id) {
-      // this websocket message was not addressed to currently
-      // viewed document. Just skip it.
-      return;
-    }
-
-    // message addressed to currently viewed document
-    switch (message.type) {
-      case 'ocrdocumenttask.taskreceived':
-        this.ocr_status = 'received';
-        break;
-      case 'ocrdocumenttask.taskstarted':
-        this.ocr_status = 'started';
-        break;
-      case 'ocrdocumenttask.tasksucceeded':
-        this.ocr_status = 'succeeded';
-        this.is_locked = false;
-        this.update_document();
-        break;
-      case 'ocrdocumenttask.taskfailed':
-        this.ocr_status = 'failed';
-        break;
-      }  // end of switch
-  }
-
   @action
   async onPanelToggle() {
     let home_folder;
@@ -98,74 +25,4 @@ export default class ViewerController extends Controller {
     }
   }
 
-  @action
-  onRunOCR() {
-    this.is_locked = true;
-    this.requests.runOCR({
-      doc_id: this.model.doc.id,
-      lang: 'deu'
-    });
-  }
-
-  get document_versions() {
-    /**
-    Returns document versions received via this.model.document_versions + additional
-    versions created via OCRing.
-
-    The point here is to update versions dropdown with newly
-    created versions (when user clicks run OCR).
-    */
-    if (this.__document_versions__) {
-      // workaround for tracking changes in array
-    }
-
-    if (this._document_versions.length > 0) {
-      // include newly OCRed versions as well
-      return this.model.document_versions.concat(
-        this._document_versions
-      );
-    }
-
-    return this.model.document_versions;
-  }
-
-  get pages() {
-
-    if (this.__pages__) {
-      // workaround for tracking changes in array
-    }
-
-    if (this._pages.length > 0) {
-      // If newer version of the pages is available
-      // (e.g. document was OCRed) then just use it
-      return this._pages;
-    }
-
-    // Initial version of the pages
-    return this.model.pages;
-  }
-
-  get isLocked() {
-    return this.is_locked;
-  }
-
-  get ocrStatus() {
-    /*
-    ocr status as received from:
-
-    1. websockets' messages for current document model
-    2. model itself
-
-    The notifications received via websockets's messages (1.) have priority
-    over current document model (2.)
-
-    Initially `this.ocr_status` will be `null` which will result in displaying
-    the actual ocr status from `this.model.doc.ocr_status`. If user chooses
-    to run OCR, the controller `this.ocr_status` will be updated in realtime
-    to non-null value (via websockets) and accordingly `this.ocr_status` will
-    take over in terms of priority.
-    */
-    return this.ocr_status || this.model.doc.ocr_status;
-  }
-
 }
diff --git a/app/routes/authenticated/document.js b/app/routes/authenticated/document.js
index 98c56ba6d313f2403933f33ef53d6e1b1c7120a9..22bf95ff97755cdca530069a44b603b9e2f1b174 100644
--- a/app/routes/authenticated/document.js
+++ b/app/routes/authenticated/document.js
@@ -21,10 +21,11 @@ export default class DocumentRoute extends Route {
       page_adapter,
       extranode,
       doc,
+      extra_doc,
       last_version,
-      last_version2,
+      extra_last_version,
       pages_with_url,
-      pages_with_url2;
+      extra_pages_with_url;
 
     page_adapter = this.store.adapterFor('page');
 
@@ -36,22 +37,30 @@ export default class DocumentRoute extends Route {
 
     last_version = doc.last_version;
 
-    pages_with_url = await page_adapter.loadImages(last_version.pages);
+    pages_with_url = await page_adapter.loadImages(last_version.pages, 'image/svg+xml');
 
     if (params.extradoc_id) {
-      doc  = await this.store.findRecord(
+      extra_doc  = await this.store.findRecord(
         'document',
         params.extradoc_id,
         { reload: true }
       );
-      last_version2 = doc.last_version
-      pages_with_url2 = await page_adapter.loadImages(last_version2.pages);
+      extra_last_version = extra_doc.last_version
+      extra_pages_with_url = await page_adapter.loadImages(
+        extra_last_version.pages
+      );
 
       return {
-        'document_version': last_version,
+        'doc': doc,
+        'document_versions': doc.versions,
+        'last_document_version': last_version,
         'pages': pages_with_url,
-        'extra_document_version': last_version2,
-        'extra_pages': pages_with_url2,
+        'extra': {
+          'doc': extra_doc,
+          'document_versions': extra_doc.versions,
+          'last_document_version': extra_last_version,
+          'pages': extra_pages_with_url,
+        }
       };
     }
 
diff --git a/app/styles/document_version.scss b/app/styles/document_version.scss
index e73fd64f983b2606f6240321c3f04c07f035a37b..dd37d4bb002317831766c0348651fa3d42be1373 100644
--- a/app/styles/document_version.scss
+++ b/app/styles/document_version.scss
@@ -1,5 +1,7 @@
 
 .thumbnails {
+  max-width: 8rem;
+
   .thumbnail {
     margin: 1rem;
     img {
diff --git a/app/templates/authenticated/viewer.hbs b/app/templates/authenticated/viewer.hbs
index 3a2f3d64af4947c6d0d27be89af6d95a775c1e9f..5436ccbae7eb267d070c9f3f8397925777e9a8a5 100644
--- a/app/templates/authenticated/viewer.hbs
+++ b/app/templates/authenticated/viewer.hbs
@@ -1,23 +1,18 @@
 <div class="panels d-flex row">
   <Viewer
-    @last_document_version={{@model.last_document_version}}
-    @document_versions={{this.document_versions}}
     @doc={{@model.doc}}
-    @pages={{this.pages}}
+    @pages={{@model.pages}}
     @extranode={{this.extranode.current_node}}
     @onPanelToggle={{this.onPanelToggle}}
     @dualPanelMode={{this.dualpanel_mode}}
-    @onRunOCR={{this.onRunOCR}}
-    @isLocked={{this.isLocked}}
-    @ocrStatus={{this.ocrStatus}}
     @hint="left" />
 
-  {{#if this.dualpanel_mode}}
-    {{#if this.extra_document_version}}
+  {{#if @model.extra}}
+    {{#if @model.extra.doc}}
       <Viewer
-        @document_version={{this.extra_document_version}}
-        @pages={{this.extra_pages}}
-        @extranode={{@model.document_version}}
+        @doc={{@model.extra.doc}}
+        @pages={{@model.extra.pages}}
+        @extranode={{this.extranode.current_node}}
         @onPanelToggle={{this.onPanelToggle}}
         @dualPanelMode={{this.dualpanel_mode}}
         @hint="right" />