diff --git a/client/assets/app.css b/client/assets/app.css
index b09310e9..6e37ca4b 100644
--- a/client/assets/app.css
+++ b/client/assets/app.css
@@ -102,3 +102,7 @@
 .box-shadow-book {
   box-shadow: 4px 1px 8px #11111166, -4px 1px 8px #11111166, 1px -4px 8px #11111166;
 }
+
+.box-shadow-side {
+  box-shadow: 4px 0px 4px #11111166;
+}
diff --git a/client/components/app/Appbar.vue b/client/components/app/Appbar.vue
index 6eef4350..3fca7849 100644
--- a/client/components/app/Appbar.vue
+++ b/client/components/app/Appbar.vue
@@ -7,15 +7,16 @@
           <span class="material-icons text-4xl text-white">arrow_back</span>
         </a>
         <h1 class="text-2xl font-book mr-6">AudioBookshelf</h1>
-
+        <!-- <div class="-mb-2">
+          <h1 class="text-lg font-book leading-3 mr-6 px-1">AudioBookshelf</h1>
+          <div class="bg-black bg-opacity-20 rounded-sm py-1.5 px-2 mt-1.5 flex items-center justify-between border border-bg">
+            <p class="text-sm text-gray-400 leading-3">My Library</p>
+            <span class="material-icons text-sm leading-3 text-gray-400">expand_more</span>
+          </div>
+        </div> -->
         <controls-global-search />
         <div class="flex-grow" />
 
-        <!-- <a v-if="isUpdateAvailable" :href="githubTagUrl" target="_blank" class="flex items-center rounded-full bg-warning p-2 text-sm">
-          <span class="material-icons">notification_important</span>
-          <span class="pl-2">Update is available! Check release notes for v{{ latestVersion }}</span>
-        </a> -->
-
         <nuxt-link v-if="userCanUpload" to="/upload" class="outline-none hover:text-gray-200 cursor-pointer w-8 h-8 flex items-center justify-center">
           <span class="material-icons">upload</span>
         </nuxt-link>
@@ -45,10 +46,8 @@
         </ui-tooltip>
         <template v-if="userCanUpdate">
           <ui-icon-btn v-show="!processingBatchDelete" icon="edit" bg-color="warning" class="mx-1.5" @click="batchEditClick" />
-          <!-- <ui-btn v-show="!processingBatchDelete" color="warning" small class="mx-2 w-10 h-10" :padding-y="0" :padding-x="0" @click="batchEditClick"><span class="material-icons text-gray-200 text-base">edit</span></ui-btn> -->
         </template>
         <ui-icon-btn v-show="userCanDelete" :disabled="processingBatchDelete" icon="delete" bg-color="error" class="mx-1.5" @click="batchDeleteClick" />
-        <!-- <ui-btn v-if="userCanDelete" color="error" small class="mx-2" :loading="processingBatchDelete" @click="batchDeleteClick"><span class="material-icons text-gray-200 pt-1">delete</span></ui-btn> -->
         <span class="material-icons text-4xl px-4 hover:text-gray-100 cursor-pointer" :class="processingBatchDelete ? 'text-gray-400' : ''" @click="cancelSelectionMode">close</span>
       </div>
     </div>
diff --git a/client/components/app/BookShelf.vue b/client/components/app/BookShelf.vue
index a1fa55e7..797453b6 100644
--- a/client/components/app/BookShelf.vue
+++ b/client/components/app/BookShelf.vue
@@ -17,17 +17,18 @@
       </div>
     </div>
     <div v-else class="w-full flex flex-col items-center">
-      <template v-for="(shelf, index) in groupedBooks">
+      <template v-for="(shelf, index) in entities">
         <div :key="index" class="w-full bookshelfRow relative">
           <div class="flex justify-center items-center">
-            <template v-for="audiobook in shelf">
-              <cards-book-card :ref="`audiobookCard-${audiobook.id}`" :key="audiobook.id" :width="bookCoverWidth" :user-progress="userAudiobooks[audiobook.id]" :audiobook="audiobook" />
+            <template v-for="entity in shelf">
+              <cards-group-card v-if="page !== ''" :key="entity.id" :width="bookCoverWidth" :group="entity" />
+              <cards-book-card v-else :key="entity.id" :width="bookCoverWidth" :user-progress="userAudiobooks[entity.id]" :audiobook="entity" />
             </template>
           </div>
           <div class="bookshelfDivider h-4 w-full absolute bottom-0 left-0 right-0 z-10" />
         </div>
       </template>
-      <div v-show="!groupedBooks.length" class="w-full py-16 text-center text-xl">
+      <div v-show="!entities.length" class="w-full py-16 text-center text-xl">
         <div class="py-4">No Audiobooks</div>
         <ui-btn v-if="filterBy !== 'all' || keywordFilter" @click="clearFilter">Clear Filter</ui-btn>
       </div>
@@ -37,11 +38,14 @@
 
 <script>
 export default {
+  props: {
+    page: String
+  },
   data() {
     return {
       width: 0,
       booksPerRow: 0,
-      groupedBooks: [],
+      entities: [],
       currFilterOrderKey: null,
       availableSizes: [60, 80, 100, 120, 140, 160, 180, 200, 220],
       selectedSizeIndex: 3,
@@ -95,13 +99,13 @@ export default {
           filterBy: 'all'
         })
       } else {
-        this.setGroupedBooks()
+        this.setBookshelfEntities()
       }
     },
     checkKeywordFilter() {
       clearTimeout(this.keywordFilterTimeout)
       this.keywordFilterTimeout = setTimeout(() => {
-        this.setGroupedBooks()
+        this.setBookshelfEntities()
       }, 500)
     },
     increaseSize() {
@@ -114,27 +118,34 @@ export default {
       this.resize()
       this.$store.dispatch('user/updateUserSettings', { bookshelfCoverSize: this.bookCoverWidth })
     },
-    setGroupedBooks() {
+    setBookshelfEntities() {
+      if (this.page === '') {
+        var audiobooksSorted = this.$store.getters['audiobooks/getFilteredAndSorted']()
+        this.currFilterOrderKey = this.filterOrderKey
+        this.setGroupedBooks(audiobooksSorted)
+      } else {
+        var entities = this.$store.getters['audiobooks/getSeriesGroups']()
+        this.setGroupedBooks(entities)
+      }
+    },
+    setGroupedBooks(entities) {
       var groups = []
       var currentRow = 0
       var currentGroup = []
 
-      var audiobooksSorted = this.$store.getters['audiobooks/getFilteredAndSorted']()
-      this.currFilterOrderKey = this.filterOrderKey
-
-      for (let i = 0; i < audiobooksSorted.length; i++) {
+      for (let i = 0; i < entities.length; i++) {
         var row = Math.floor(i / this.booksPerRow)
         if (row > currentRow) {
           groups.push([...currentGroup])
           currentRow = row
           currentGroup = []
         }
-        currentGroup.push(audiobooksSorted[i])
+        currentGroup.push(entities[i])
       }
       if (currentGroup.length) {
         groups.push([...currentGroup])
       }
-      this.groupedBooks = groups
+      this.entities = groups
     },
     calculateBookshelf() {
       this.width = this.$refs.wrapper.clientWidth
@@ -142,12 +153,6 @@ export default {
       var booksPerRow = Math.floor(this.width / this.bookWidth)
       this.booksPerRow = booksPerRow
     },
-    getAudiobookCard(id) {
-      if (this.$refs[`audiobookCard-${id}`] && this.$refs[`audiobookCard-${id}`].length) {
-        return this.$refs[`audiobookCard-${id}`][0]
-      }
-      return null
-    },
     init() {
       var bookshelfCoverSize = this.$store.getters['user/getUserSetting']('bookshelfCoverSize')
       var sizeIndex = this.availableSizes.findIndex((s) => s === bookshelfCoverSize)
@@ -157,16 +162,16 @@ export default {
     resize() {
       this.$nextTick(() => {
         this.calculateBookshelf()
-        this.setGroupedBooks()
+        this.setBookshelfEntities()
       })
     },
     audiobooksUpdated() {
       console.log('[AudioBookshelf] Audiobooks Updated')
-      this.setGroupedBooks()
+      this.setBookshelfEntities()
     },
     settingsUpdated(settings) {
       if (this.currFilterOrderKey !== this.filterOrderKey) {
-        this.setGroupedBooks()
+        this.setBookshelfEntities()
       }
       if (settings.bookshelfCoverSize !== this.bookCoverWidth && settings.bookshelfCoverSize !== undefined) {
         var index = this.availableSizes.indexOf(settings.bookshelfCoverSize)
diff --git a/client/components/app/SideRail.vue b/client/components/app/SideRail.vue
new file mode 100644
index 00000000..a9239632
--- /dev/null
+++ b/client/components/app/SideRail.vue
@@ -0,0 +1,71 @@
+<template>
+  <div class="w-20 border-r border-primary bg-bg h-full relative box-shadow-side z-20">
+    <nuxt-link to="/library" class="w-full h-20 flex flex-col items-center justify-center text-white border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="paramId === '' ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
+      <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
+        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
+      </svg>
+
+      <p class="font-book pt-1.5" style="font-size: 0.8rem">Library</p>
+
+      <div v-show="paramId === ''" class="h-0.5 w-full bg-yellow-400 absolute bottom-0 left-0" />
+    </nuxt-link>
+
+    <nuxt-link to="/library/series" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="paramId === 'series' ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
+      <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
+        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7m0 10a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2h-2a2 2 0 00-2 2" />
+      </svg>
+
+      <p class="font-book pt-1.5" style="font-size: 0.8rem">Series</p>
+
+      <div v-show="paramId === 'series'" class="h-0.5 w-full bg-yellow-400 absolute bottom-0 left-0" />
+    </nuxt-link>
+
+    <nuxt-link to="/library/collections" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="paramId === 'collections' ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
+      <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
+        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
+      </svg>
+
+      <p class="font-book pt-1.5" style="font-size: 0.8rem">Collections</p>
+
+      <div v-show="paramId === 'collections'" class="h-0.5 w-full bg-yellow-400 absolute bottom-0 left-0" />
+    </nuxt-link>
+
+    <nuxt-link to="/library/tags" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="paramId === 'tags' ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
+      <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
+        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" />
+      </svg>
+
+      <p class="font-book pt-1.5" style="font-size: 0.8rem">Tags</p>
+
+      <div v-show="paramId === 'tags'" class="h-0.5 w-full bg-yellow-400 absolute bottom-0 left-0" />
+    </nuxt-link>
+
+    <nuxt-link to="/library/authors" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="paramId === 'authors' ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
+      <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
+        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
+      </svg>
+
+      <p class="font-book pt-1.5" style="font-size: 0.8rem">Authors</p>
+
+      <div v-show="paramId === 'authors'" class="h-0.5 w-full bg-yellow-400 absolute bottom-0 left-0" />
+    </nuxt-link>
+  </div>
+</template>
+
+<script>
+export default {
+  data() {
+    return {}
+  },
+  computed: {
+    paramId() {
+      return this.$route.params ? this.$route.params.id || '' : ''
+    },
+    selectedClassName() {
+      return ''
+    }
+  },
+  methods: {},
+  mounted() {}
+}
+</script>
\ No newline at end of file
diff --git a/client/components/cards/GroupCard.vue b/client/components/cards/GroupCard.vue
new file mode 100644
index 00000000..41f528bf
--- /dev/null
+++ b/client/components/cards/GroupCard.vue
@@ -0,0 +1,56 @@
+<template>
+  <div class="relative">
+    <div class="rounded-sm h-full overflow-hidden relative" :style="{ padding: `16px ${paddingX}px` }" @mouseover="isHovering = true" @mouseleave="isHovering = false" @click="clickCard">
+      <nuxt-link :to="`/library`" class="cursor-pointer">
+        <div class="w-full relative box-shadow-book bg-primary" :style="{ height: height + 'px', width: height + 'px' }"></div>
+      </nuxt-link>
+    </div>
+    <!-- <div :style="{ width: height + 'px', height: height + 'px' }" class="box-shadow-book bg-primary">
+    <p class="text-white">{{ groupName }}</p>
+  </div> -->
+  </div>
+</template>
+
+<script>
+export default {
+  props: {
+    group: {
+      type: Object,
+      default: () => null
+    },
+    width: {
+      type: Number,
+      default: 120
+    }
+  },
+  data() {
+    return {
+      isHovering: false
+    }
+  },
+  computed: {
+    _group() {
+      return this.group || {}
+    },
+    height() {
+      return this.width * 1.6
+    },
+    sizeMultiplier() {
+      return this.width / 120
+    },
+    paddingX() {
+      return 16 * this.sizeMultiplier
+    },
+    books() {
+      return this._group.books || []
+    },
+    groupName() {
+      return this._group.name || 'No Name'
+    }
+  },
+  methods: {
+    clickCard() {}
+  },
+  mounted() {}
+}
+</script>
\ No newline at end of file
diff --git a/client/layouts/default.vue b/client/layouts/default.vue
index 01f3883f..8cd11e52 100644
--- a/client/layouts/default.vue
+++ b/client/layouts/default.vue
@@ -1,7 +1,9 @@
 <template>
   <div class="text-white max-h-screen h-screen overflow-hidden bg-bg">
     <app-appbar />
+
     <Nuxt />
+
     <app-stream-container ref="streamContainer" />
     <modals-edit-modal />
     <widgets-scan-alert />
diff --git a/client/pages/index.vue b/client/pages/index.vue
index 2051a0c4..8ae060b9 100644
--- a/client/pages/index.vue
+++ b/client/pages/index.vue
@@ -1,7 +1,12 @@
 <template>
   <div class="page" :class="streamAudiobook ? 'streaming' : ''">
     <app-book-shelf-toolbar />
+    <!-- <div class="flex h-full">
+      <app-side-rail />
+      <div class="flex-grow"> -->
     <app-book-shelf />
+    <!-- </div> -->
+    <!-- </div> -->
   </div>
 </template>
 
diff --git a/client/pages/library/_id.vue b/client/pages/library/_id.vue
new file mode 100644
index 00000000..1988d19a
--- /dev/null
+++ b/client/pages/library/_id.vue
@@ -0,0 +1,31 @@
+<template>
+  <div class="page" :class="streamAudiobook ? 'streaming' : ''">
+    <div class="flex h-full">
+      <app-side-rail />
+      <div class="flex-grow">
+        <app-book-shelf-toolbar />
+        <app-book-shelf :page="id || ''" />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  asyncData({ params }) {
+    return {
+      id: params.id
+    }
+  },
+  data() {
+    return {}
+  },
+  computed: {
+    streamAudiobook() {
+      return this.$store.state.streamAudiobook
+    }
+  },
+  methods: {},
+  mounted() {}
+}
+</script>
\ No newline at end of file
diff --git a/client/store/audiobooks.js b/client/store/audiobooks.js
index d3a655fb..09643604 100644
--- a/client/store/audiobooks.js
+++ b/client/store/audiobooks.js
@@ -63,6 +63,23 @@ export const getters = {
       return value
     })
   },
+  getSeriesGroups: (state, getters, rootState) => () => {
+    var series = {}
+    state.audiobooks.forEach((audiobook) => {
+      if (audiobook.book && audiobook.book.series) {
+        if (series[audiobook.book.series]) {
+          series[audiobook.book.series].books.push(audiobook)
+        } else {
+          series[audiobook.book.series] = {
+            type: 'series',
+            name: audiobook.book.series,
+            books: [audiobook]
+          }
+        }
+      }
+    })
+    return Object.values(series)
+  },
   getUniqueAuthors: (state) => {
     var _authors = state.audiobooks.filter(ab => !!(ab.book && ab.book.author)).map(ab => ab.book.author)
     return [...new Set(_authors)].sort((a, b) => a.toLowerCase() < b.toLowerCase() ? -1 : 1)
diff --git a/package.json b/package.json
index 6653ba08..ff6b24ea 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,17 @@
     "dev": "node index.js",
     "start": "node index.js",
     "client": "cd client && npm install && npm run generate",
-    "prod": "npm run client && npm install && node prod.js"
+    "prod": "npm run client && npm install && node prod.js",
+    "build-win": "cd client && npm run generate && cd .. && pkg -t node12-win-x64 -o ./dist/app .",
+    "build-linux": "pkg -t node12-linux-arm64 -o ./dist/app ."
+  },
+  "bin": "prod.js",
+  "pkg": {
+    "assets": "client/dist/**/*",
+    "scripts": [
+      "prod.js",
+      "server/**/*.js"
+    ]
   },
   "author": "advplyr",
   "license": "ISC",
diff --git a/prod.js b/prod.js
index 628f1e2f..8d457746 100644
--- a/prod.js
+++ b/prod.js
@@ -13,6 +13,7 @@ process.env.TOKEN_SECRET = '09f26e402586e2faa8da4c98a35f1b20d6b033c6097befa8be34
 process.env.NODE_ENV = 'production'
 
 const server = require('./server/Server')
+
 global.appRoot = __dirname
 
 var inputConfig = options.config ? Path.resolve(options.config) : null
@@ -24,7 +25,7 @@ const CONFIG_PATH = inputConfig || process.env.CONFIG_PATH || Path.resolve('conf
 const AUDIOBOOK_PATH = inputAudiobook || process.env.AUDIOBOOK_PATH || Path.resolve('audiobooks')
 const METADATA_PATH = inputMetadata || process.env.METADATA_PATH || Path.resolve('metadata')
 
-console.log('Config', CONFIG_PATH, METADATA_PATH, AUDIOBOOK_PATH)
+console.log(process.env.NODE_ENV, 'Config', CONFIG_PATH, METADATA_PATH, AUDIOBOOK_PATH)
 
 const Server = new server(PORT, CONFIG_PATH, METADATA_PATH, AUDIOBOOK_PATH)
 Server.start()
diff --git a/server/Server.js b/server/Server.js
index 8ec318cf..5fb723e0 100644
--- a/server/Server.js
+++ b/server/Server.js
@@ -113,6 +113,7 @@ class Server {
     await this.streamManager.ensureStreamsDir()
     await this.streamManager.removeOrphanStreams()
     await this.downloadManager.removeOrphanDownloads()
+
     await this.db.init()
     this.auth.init()
 
@@ -171,7 +172,6 @@ class Server {
 
   async start() {
     Logger.info('=== Starting Server ===')
-
     await this.init()
 
     const app = express()