diff --git a/changes/changelog.d/572.enhancement b/changes/changelog.d/572.enhancement
new file mode 100644
index 0000000000000000000000000000000000000000..cb75911db6bbe484c220c45548dfe73ab8f97231
--- /dev/null
+++ b/changes/changelog.d/572.enhancement
@@ -0,0 +1 @@
+Preload next track in queue (#572)
diff --git a/front/src/EmbedFrame.vue b/front/src/EmbedFrame.vue
index 7dcc371ea5ccefe07881dbcd630bcc2b428249fe..90aa95e7d8c4b46732f3bda4739066c2c2248db2 100644
--- a/front/src/EmbedFrame.vue
+++ b/front/src/EmbedFrame.vue
@@ -357,7 +357,8 @@ export default {
       this.$nextTick(() => {
         self.bindEvents()
         if (self.tracks.length > 0) {
-          var topPos = document.getElementById(`queue-item-${v}`).offsetTop;
+          let el = document.getElementById(`queue-item-${v}`);
+          var topPos = el.offsetTop;
           document.getElementById('queue').scrollTop = topPos-10;
         }
       })
diff --git a/front/src/components/audio/Player.vue b/front/src/components/audio/Player.vue
index 74d78fc31f62f74b518eaa3bc7936cef02e33015..dd0f921d8b2c099e94e410b64064072dcf5e6bc4 100644
--- a/front/src/components/audio/Player.vue
+++ b/front/src/components/audio/Player.vue
@@ -1,16 +1,6 @@
 <template>
   <section class="ui inverted segment player-wrapper" :aria-label="labels.audioPlayer" :style="style">
     <div class="player">
-      <audio-track
-        ref="currentAudio"
-        v-if="currentTrack"
-        @errored="handleError"
-        :is-current="true"
-        :start-time="$store.state.player.currentTime"
-        :autoplay="$store.state.player.playing"
-        :key="audioKey"
-        :track="currentTrack">
-      </audio-track>
       <div v-if="currentTrack" class="track-area ui unstackable items">
         <div class="ui inverted item">
           <div class="ui tiny image">
@@ -53,7 +43,7 @@
       <div class="progress-area" v-if="currentTrack && !errored">
         <div class="ui grid">
           <div class="left floated four wide column">
-            <p class="timer start" @click="updateProgress(0)">{{currentTimeFormatted}}</p>
+            <p class="timer start" @click="setCurrentTime(0)">{{currentTimeFormatted}}</p>
           </div>
 
           <div v-if="!isLoadingAudio" class="right floated four wide column">
@@ -230,8 +220,10 @@ import GlobalEvents from "@/components/utils/global-events"
 import ColorThief from "@/vendor/color-thief"
 import { Howl } from "howler"
 import $ from 'jquery'
+import _ from '@/lodash'
+import url from '@/utils/url'
+import axios from 'axios'
 
-import AudioTrack from "@/components/audio/Track"
 import TrackFavoriteIcon from "@/components/favorites/TrackFavoriteIcon"
 import TrackPlaylistIcon from "@/components/playlists/TrackPlaylistIcon"
 
@@ -240,7 +232,6 @@ export default {
     TrackFavoriteIcon,
     TrackPlaylistIcon,
     GlobalEvents,
-    AudioTrack
   },
   data() {
     let defaultAmbiantColors = [
@@ -255,8 +246,14 @@ export default {
       defaultAmbiantColors: defaultAmbiantColors,
       showVolume: false,
       ambiantColors: defaultAmbiantColors,
-      audioKey: String(new Date()),
-      dummyAudio: null
+      currentSound: null,
+      dummyAudio: null,
+      isUpdatingTime: false,
+      sourceErrors: 0,
+      progressInterval: null,
+      maxPreloaded: 3,
+      preloadDelay: 15,
+      soundsCache: [],
     }
   },
   mounted() {
@@ -270,9 +267,13 @@ export default {
       autoplay: false,
       src: ["noop.webm", "noop.mp3"]
     })
+    if (this.currentTrack) {
+      this.getSound(this.currentTrack)
+    }
   },
   destroyed() {
     this.dummyAudio.unload()
+    this.observeProgress(false)
   },
   methods: {
     ...mapActions({
@@ -280,8 +281,24 @@ export default {
       mute: "player/mute",
       unmute: "player/unmute",
       clean: "queue/clean",
-      updateProgress: "player/updateProgress"
     }),
+    async getTrackData (trackData) {
+      let data = null
+      if (!trackData.uploads.length || trackData.uploads.length === 0) {
+        // we don't have upload informations for this track, we need to fetch it
+        await axios.get(`tracks/${trackData.id}/`).then((response) => {
+          data = response.data
+        }, error => {
+          data = null
+        })
+      } else {
+        return trackData
+      }
+      if (data === null) {
+        return
+      }
+      return data
+    },
     shuffle() {
       let disabled = this.queue.tracks.length === 0
       if (this.isShuffling || disabled) {
@@ -316,7 +333,7 @@ export default {
       let time
       let target = this.$refs.progress
       time = (e.layerX / target.offsetWidth) * this.duration
-      this.$refs.currentAudio.setCurrentTime(time)
+      this.setCurrentTime(time)
     },
     updateBackground() {
       // delete existing canvas, if any
@@ -331,6 +348,222 @@ export default {
     handleError({ sound, error }) {
       this.$store.commit("player/isLoadingAudio", false)
       this.$store.dispatch("player/trackErrored")
+    },
+    getSound (trackData) {
+      let cached = this.getSoundFromCache(trackData)
+      if (cached) {
+        return cached.sound
+      }
+      let srcs = this.getSrcs(trackData)
+      let self = this
+      let sound = new Howl({
+        src: srcs.map((s) => { return s.url }),
+        format: srcs.map((s) => { return s.type }),
+        autoplay: false,
+        loop: false,
+        html5: true,
+        preload: true,
+        volume: this.volume,
+        onend: function () {
+          self.ended()
+        },
+        onunlock: function () {
+          if (this.$store.state.player.playing) {
+            self.sound.play()
+          }
+        },
+        onload: function () {
+          let sound = this
+          let node = this._sounds[0]._node;
+          node.addEventListener('progress', () => {
+            if (sound != self.currentSound) {
+              return
+            }
+            self.updateBuffer(node)
+          })
+        },
+        onplay: function () {
+          self.$store.commit('player/isLoadingAudio', false)
+          self.$store.commit('player/resetErrorCount')
+          self.$store.commit('player/errored', false)
+          self.$store.commit('player/duration', this.duration())
+        },
+        onloaderror: function (sound, error) {
+          if (this != self.currentSound) {
+            return
+          }
+          console.log('Error while playing:', sound, error)
+          self.handleError({sound, error})
+        },
+      })
+      this.addSoundToCache(sound, trackData)
+      return sound
+    },
+    getSrcs: function (trackData) {
+      let sources = trackData.uploads.map(u => {
+        return {
+          type: u.extension,
+          url: this.$store.getters['instance/absoluteUrl'](u.listen_url),
+        }
+      })
+      // We always add a transcoded MP3 src at the end
+      // because transcoding is expensive, but we want browsers that do
+      // not support other codecs to be able to play it :)
+      sources.push({
+        type: 'mp3',
+        url: url.updateQueryString(
+          this.$store.getters['instance/absoluteUrl'](trackData.listen_url),
+          'to',
+          'mp3'
+        )
+      })
+      if (this.$store.state.auth.authenticated) {
+        // we need to send the token directly in url
+        // so authentication can be checked by the backend
+        // because for audio files we cannot use the regular Authentication
+        // header
+        sources.forEach(e => {
+          e.url = url.updateQueryString(e.url, 'jwt', this.$store.state.auth.token)
+        })
+      }
+      return sources
+    },
+
+    updateBuffer (node) {
+      // from https://github.com/goldfire/howler.js/issues/752#issuecomment-372083163
+      let range = 0;
+      let bf = node.buffered;
+      let time = node.currentTime;
+      try {
+        while(!(bf.start(range) <= time && time <= bf.end(range))) {
+          range += 1;
+        }
+      } catch (IndexSizeError) {
+        return
+      }
+      let loadPercentage
+      let start =  bf.start(range)
+      let end =  bf.end(range)
+      if (range === 0) {
+        // easy case, no user-seek
+        let loadStartPercentage = start / node.duration;
+        let loadEndPercentage = end / node.duration;
+        loadPercentage = loadEndPercentage - loadStartPercentage;
+      } else {
+        let loaded = end - start
+        let remainingToLoad = node.duration - start
+        // user seeked a specific position in the audio, our progress must be
+        // computed based on the remaining portion of the track
+        loadPercentage = loaded / remainingToLoad;
+      }
+      if (loadPercentage * 100 === this.bufferProgress) {
+        return
+      }
+      this.$store.commit('player/bufferProgress', loadPercentage * 100)
+    },
+    updateProgress: function () {
+      this.isUpdatingTime = true
+      if (this.currentSound && this.currentSound.state() === 'loaded') {
+        let t = this.currentSound.seek()
+        let d = this.currentSound.duration()
+        this.$store.dispatch('player/updateProgress', t)
+        this.updateBuffer(this.currentSound._sounds[0]._node)
+        let toPreload = this.$store.state.queue.tracks[this.currentIndex + 1]
+        if (toPreload && !this.getSoundFromCache(toPreload) && (t > this.preloadDelay || d - t < 30)) {
+          this.getSound(toPreload)
+        }
+      }
+    },
+    observeProgress: function (enable) {
+      let self = this
+      if (enable) {
+        if (self.progressInterval) {
+          clearInterval(self.progressInterval)
+        }
+        self.progressInterval = setInterval(() => {
+          self.updateProgress()
+        }, 1000)
+      } else {
+        clearInterval(self.progressInterval)
+      }
+    },
+    setCurrentTime (t) {
+      if (t < 0 | t > this.duration) {
+        return
+      }
+      if (!this.currentSound || !this.currentSound._sounds[0]) {
+        return
+      }
+      if (t === this.currentSound.seek()) {
+        return
+      }
+      if (t === 0) {
+        this.updateProgressThrottled.cancel()
+      }
+      this.currentSound.seek(t)
+    },
+    ended: function () {
+      let onlyTrack = this.$store.state.queue.tracks.length === 1
+      if (this.looping === 1 || (onlyTrack && this.looping === 2)) {
+        this.currentSound.seek(0)
+        this.currentSound.play()
+      } else {
+        this.$store.dispatch('player/trackEnded', this.currentTrack)
+      }
+    },
+    getSoundFromCache (trackData) {
+      return this.soundsCache.filter((d) => {
+        if (d.track.id !== trackData.id) {
+          return false
+        }
+
+        return true
+      })[0]
+    },
+    addSoundToCache (sound, trackData) {
+      let data = {
+        date: new Date(),
+        track: trackData,
+        sound: sound
+      }
+      this.soundsCache.push(data)
+      this.checkCache()
+    },
+    checkCache () {
+      let self = this
+      let toKeep = []
+      _.reverse(this.soundsCache).forEach((e) => {
+        if (toKeep.length < self.maxPreloaded) {
+          toKeep.push(e)
+        } else {
+          let src = e.sound._src
+          e.sound.unload()
+        }
+      })
+      this.soundsCache = _.reverse(toKeep)
+    },
+    async loadSound (newValue, oldValue) {
+      let trackData = newValue
+      let oldSound = this.currentSound
+      if (oldSound && trackData !== oldValue) {
+        oldSound.pause()
+      }
+      if (!trackData) {
+        return
+      }
+      if (!this.isShuffling && trackData != oldValue) {
+        trackData = await this.getTrackData(trackData)
+        if (trackData === null) {
+          this.handleError({})
+        }
+        this.currentSound = this.getSound(trackData)
+        this.$store.commit('player/isLoadingAudio', true)
+        if (this.playing) {
+          this.currentSound.play()
+          this.$store.commit('player/playing', true)
+          this.observeProgress(true)
+        }
+      }
     }
   },
   computed: {
@@ -343,6 +576,7 @@ export default {
       duration: state => state.player.duration,
       bufferProgress: state => state.player.bufferProgress,
       errored: state => state.player.errored,
+      currentTime: state => state.player.currentTime,
       queue: state => state.queue
     }),
     ...mapGetters({
@@ -353,6 +587,9 @@ export default {
       currentTimeFormatted: "player/currentTimeFormatted",
       progress: "player/progress"
     }),
+    updateProgressThrottled () {
+      return _.throttle(this.updateProgress, 250)
+    },
     labels() {
       let audioPlayer = this.$pgettext('Sidebar/Player/Hidden text', "Media player")
       let previousTrack = this.$pgettext('Sidebar/Player/Icon.Tooltip', "Previous track")
@@ -414,22 +651,45 @@ export default {
         })
         .join(", ")
       return gradients
-    }
+    },
   },
   watch: {
-    currentTrack(newValue, oldValue) {
-      if (!this.isShuffling && newValue != oldValue) {
-        this.audioKey = String(new Date())
-      }
-      if (!newValue || !newValue.album.cover) {
-        this.ambiantColors = this.defaultAmbiantColors
-      }
+    currentTrack: {
+      async handler (newValue, oldValue) {
+        await this.loadSound(newValue, oldValue)
+        if (!newValue || !trackData.album.cover) {
+          this.ambiantColors = this.defaultAmbiantColors
+        }
+      },
+      immediate: false
     },
     volume(newValue) {
       this.sliderVolume = newValue
+      if (this.currentSound) {
+        this.currentSound.volume(newValue)
+      }
     },
     sliderVolume(newValue) {
       this.$store.commit("player/volume", newValue)
+    },
+    playing: async function (newValue) {
+      if (this.currentSound) {
+        if (newValue === true) {
+          this.currentSound.play()
+        } else {
+          this.currentSound.pause()
+        }
+      } else {
+        await this.loadSound(this.currentTrack, null)
+      }
+
+      this.observeProgress(newValue)
+    },
+    currentTime (newValue) {
+      if (!this.isUpdatingTime) {
+        this.setCurrentTime(newValue)
+      }
+      this.isUpdatingTime = false
     }
   }
 }
diff --git a/front/src/components/audio/Track.vue b/front/src/components/audio/Track.vue
deleted file mode 100644
index 2e0f8c421a0e780247224b93764b55fd476ae541..0000000000000000000000000000000000000000
--- a/front/src/components/audio/Track.vue
+++ /dev/null
@@ -1,225 +0,0 @@
-<template>
-  <i />
-</template>
-
-<script>
-import {mapState} from 'vuex'
-import _ from '@/lodash'
-import url from '@/utils/url'
-import {Howl} from 'howler'
-import axios from 'axios'
-
-// import logger from '@/logging'
-
-export default {
-  props: {
-    track: {type: Object},
-    isCurrent: {type: Boolean, default: false},
-    startTime: {type: Number, default: 0},
-    autoplay: {type: Boolean, default: false}
-  },
-  data () {
-    return {
-      trackData: this.track,
-      sourceErrors: 0,
-      sound: null,
-      isUpdatingTime: false,
-      progressInterval: null
-    }
-  },
-  mounted () {
-    let self = this
-    if (!this.trackData.uploads.length || this.trackData.uploads.length === 0) {
-      // we don't have upload informations for this track, we need to fetch it
-      axios.get(`tracks/${this.trackData.id}/`).then((response) => {
-        self.trackData = response.data
-        self.setupSound()
-      }, error => {
-        self.$emit('errored', {})
-      })
-    } else {
-      this.setupSound()
-    }
-  },
-  destroyed () {
-    this.observeProgress(false)
-    this.sound.unload()
-  },
-  computed: {
-    ...mapState({
-      playing: state => state.player.playing,
-      currentTime: state => state.player.currentTime,
-      duration: state => state.player.duration,
-      volume: state => state.player.volume,
-      looping: state => state.player.looping
-    }),
-    srcs: function () {
-      let sources = this.trackData.uploads.map(u => {
-        return {
-          type: u.extension,
-          url: this.$store.getters['instance/absoluteUrl'](u.listen_url),
-        }
-      })
-      // We always add a transcoded MP3 src at the end
-      // because transcoding is expensive, but we want browsers that do
-      // not support other codecs to be able to play it :)
-      sources.push({
-        type: 'mp3',
-        url: url.updateQueryString(
-          this.$store.getters['instance/absoluteUrl'](this.trackData.listen_url),
-          'to',
-          'mp3'
-        )
-      })
-      if (this.$store.state.auth.authenticated) {
-        // we need to send the token directly in url
-        // so authentication can be checked by the backend
-        // because for audio files we cannot use the regular Authentication
-        // header
-        sources.forEach(e => {
-          e.url = url.updateQueryString(e.url, 'jwt', this.$store.state.auth.token)
-        })
-      }
-      return sources
-    },
-    updateProgressThrottled () {
-      return _.throttle(this.updateProgress, 250)
-    }
-  },
-  methods: {
-    setupSound () {
-      let self = this
-      this.sound = new Howl({
-        src: this.srcs.map((s) => { return s.url }),
-        format: this.srcs.map((s) => { return s.type }),
-        autoplay: false,
-        loop: false,
-        html5: true,
-        preload: true,
-        volume: this.volume,
-        onend: function () {
-          self.ended()
-        },
-        onunlock: function () {
-          if (this.$store.state.player.playing) {
-            self.sound.play()
-          }
-        },
-        onload: function () {
-          self.$store.commit('player/isLoadingAudio', false)
-          self.$store.commit('player/resetErrorCount')
-          self.$store.commit('player/errored', false)
-          self.$store.commit('player/duration', self.sound.duration())
-          let node = self.sound._sounds[0]._node;
-          node.addEventListener('progress', () => {
-            self.updateBuffer(node)
-          })
-        },
-        onloaderror: function (sound, error) {
-          console.log('Error while playing:', sound, error)
-          self.$emit('errored', {sound, error})
-        },
-      })
-      if (this.autoplay) {
-        self.$store.commit('player/isLoadingAudio', true)
-        this.sound.play()
-        this.$store.commit('player/playing', true)
-        this.observeProgress(true)
-      }
-    },
-    updateBuffer (node) {
-      // from https://github.com/goldfire/howler.js/issues/752#issuecomment-372083163
-      let range = 0;
-      let bf = node.buffered;
-      let time = node.currentTime;
-      try {
-        while(!(bf.start(range) <= time && time <= bf.end(range))) {
-          range += 1;
-        }
-      } catch (IndexSizeError) {
-        return
-      }
-      let loadPercentage
-      let start =  bf.start(range)
-      let end =  bf.end(range)
-      if (range === 0) {
-        // easy case, no user-seek
-        let loadStartPercentage = start / node.duration;
-        let loadEndPercentage = end / node.duration;
-        loadPercentage = loadEndPercentage - loadStartPercentage;
-      } else {
-        let loaded = end - start
-        let remainingToLoad = node.duration - start
-        // user seeked a specific position in the audio, our progress must be
-        // computed based on the remaining portion of the track
-        loadPercentage = loaded / remainingToLoad;
-      }
-      this.$store.commit('player/bufferProgress', loadPercentage * 100)
-    },
-    updateProgress: function () {
-      this.isUpdatingTime = true
-      if (this.sound && this.sound.state() === 'loaded') {
-        this.$store.dispatch('player/updateProgress', this.sound.seek())
-        this.updateBuffer(this.sound._sounds[0]._node)
-      }
-    },
-    observeProgress: function (enable) {
-      let self = this
-      if (enable) {
-        if (self.progressInterval) {
-          clearInterval(self.progressInterval)
-        }
-        self.progressInterval = setInterval(() => {
-          self.updateProgress()
-        }, 1000)
-      } else {
-        clearInterval(self.progressInterval)
-      }
-    },
-    setCurrentTime (t) {
-      if (t < 0 | t > this.duration) {
-        return
-      }
-      if (t === this.sound.seek()) {
-        return
-      }
-      if (t === 0) {
-        this.updateProgressThrottled.cancel()
-      }
-      this.sound.seek(t)
-    },
-    ended: function () {
-      let onlyTrack = this.$store.state.queue.tracks.length === 1
-      if (this.looping === 1 || (onlyTrack && this.looping === 2)) {
-        this.sound.seek(0)
-        this.sound.play()
-      } else {
-        this.$store.dispatch('player/trackEnded', this.trackData)
-      }
-    }
-  },
-  watch: {
-    playing: function (newValue) {
-      if (newValue === true) {
-        this.sound.play()
-      } else {
-        this.sound.pause()
-      }
-      this.observeProgress(newValue)
-    },
-    volume: function (newValue) {
-      this.sound.volume(newValue)
-    },
-    currentTime (newValue) {
-      if (!this.isUpdatingTime) {
-        this.setCurrentTime(newValue)
-      }
-      this.isUpdatingTime = false
-    }
-  }
-}
-</script>
-
-<!-- Add "scoped" attribute to limit CSS to this component only -->
-<style scoped>
-</style>
diff --git a/front/src/components/library/AlbumBase.vue b/front/src/components/library/AlbumBase.vue
index 3ff07b10af8af5d8fdd254330f96f6aee500e0ec..1f89bef8898f9a1a496545688ff818a10e071e2a 100644
--- a/front/src/components/library/AlbumBase.vue
+++ b/front/src/components/library/AlbumBase.vue
@@ -74,7 +74,7 @@
                     <translate translate-context="Content/Moderation/Link">Open in moderation interface</translate>
                   </router-link>
                   <a
-                    v-if="$store.state.auth.profile.is_superuser"
+                    v-if="$store.state.auth.profile && $store.state.auth.profile.is_superuser"
                     class="basic item"
                     :href="$store.getters['instance/absoluteUrl'](`/api/admin/music/album/${object.id}`)"
                     target="_blank" rel="noopener noreferrer">
diff --git a/front/src/components/library/ArtistBase.vue b/front/src/components/library/ArtistBase.vue
index 71f3abd5ca2a31247d950786ce7bd41f400541d1..5da7370bacf5fba3abe0c44aa10eb82d421c4a46 100644
--- a/front/src/components/library/ArtistBase.vue
+++ b/front/src/components/library/ArtistBase.vue
@@ -85,7 +85,7 @@
                     <translate translate-context="Content/Moderation/Link">Open in moderation interface</translate>
                   </router-link>
                   <a
-                    v-if="$store.state.auth.profile.is_superuser"
+                    v-if="$store.state.auth.profile && $store.state.auth.profile.is_superuser"
                     class="basic item"
                     :href="$store.getters['instance/absoluteUrl'](`/api/admin/music/artist/${object.id}`)"
                     target="_blank" rel="noopener noreferrer">
diff --git a/front/src/components/library/TrackBase.vue b/front/src/components/library/TrackBase.vue
index 6df2f7aa0caabde16847f7deb8f8aa2faedbc722..639c8f51b83c63166d1ec4d70d668a82352e48a1 100644
--- a/front/src/components/library/TrackBase.vue
+++ b/front/src/components/library/TrackBase.vue
@@ -93,7 +93,7 @@
                     <translate translate-context="Content/Moderation/Link">Open in moderation interface</translate>
                   </router-link>
                   <a
-                    v-if="$store.state.auth.profile.is_superuser"
+                    v-if="$store.state.auth.profile && $store.state.auth.profile.is_superuser"
                     class="basic item"
                     :href="$store.getters['instance/absoluteUrl'](`/api/admin/music/track/${track.id}`)"
                     target="_blank" rel="noopener noreferrer">
diff --git a/front/src/lodash.js b/front/src/lodash.js
index afc53da2686668bfe4f28fed3f5a657f45786885..8cd3ed92f41e86b0eff9b88bd5af923742b1864e 100644
--- a/front/src/lodash.js
+++ b/front/src/lodash.js
@@ -11,4 +11,5 @@ export default {
   throttle: require('lodash/throttle'),
   uniq: require('lodash/uniq'),
   remove: require('lodash/remove'),
+  reverse: require('lodash/reverse'),
 }
diff --git a/front/src/store/index.js b/front/src/store/index.js
index e46aea86de226a678eaa27c0666249a44a526941..791dbb1e92fd1d3add3509ea67575dc734c64dd8 100644
--- a/front/src/store/index.js
+++ b/front/src/store/index.js
@@ -54,8 +54,7 @@ export default new Vuex.Store({
       paths: [
         'player.looping',
         'player.volume',
-        'player.duration',
-        'player.errored'],
+        'player.duration'],
       filter: (mutation) => {
         return mutation.type.startsWith('player/') && mutation.type !== 'player/currentTime'
       }
diff --git a/front/src/views/admin/library/AlbumDetail.vue b/front/src/views/admin/library/AlbumDetail.vue
index 8de907a72bcb8d07671156e27939cc2195028c6c..b89afb945d7003cef05c7a92a68c8404b4e2e1c0 100644
--- a/front/src/views/admin/library/AlbumDetail.vue
+++ b/front/src/views/admin/library/AlbumDetail.vue
@@ -35,7 +35,7 @@
                     <i class="dropdown icon"></i>
                     <div class="menu">
                       <a
-                        v-if="$store.state.auth.profile.is_superuser"
+                        v-if="$store.state.auth.profile && $store.state.auth.profile.is_superuser"
                         class="basic item"
                         :href="$store.getters['instance/absoluteUrl'](`/api/admin/music/album/${object.id}`)"
                         target="_blank" rel="noopener noreferrer">
diff --git a/front/src/views/admin/library/ArtistDetail.vue b/front/src/views/admin/library/ArtistDetail.vue
index 91b3542b2dc1c93721f4036699f6f84aa90643cb..0c4175bae4a84a68bb1f526deafd1af2fce42cea 100644
--- a/front/src/views/admin/library/ArtistDetail.vue
+++ b/front/src/views/admin/library/ArtistDetail.vue
@@ -34,7 +34,7 @@
                     <i class="dropdown icon"></i>
                     <div class="menu">
                       <a
-                        v-if="$store.state.auth.profile.is_superuser"
+                        v-if="$store.state.auth.profile && $store.state.auth.profile.is_superuser"
                         class="basic item"
                         :href="$store.getters['instance/absoluteUrl'](`/api/admin/music/artist/${object.id}`)"
                         target="_blank" rel="noopener noreferrer">
diff --git a/front/src/views/admin/library/LibraryDetail.vue b/front/src/views/admin/library/LibraryDetail.vue
index a4df048da668db2f77ef304052204d09d69f4bf1..beec7e2b408cf6b032c5f0114c979d77f958a64c 100644
--- a/front/src/views/admin/library/LibraryDetail.vue
+++ b/front/src/views/admin/library/LibraryDetail.vue
@@ -27,7 +27,7 @@
 
                 <div class="ui icon buttons">
                   <a
-                    v-if="$store.state.auth.profile.is_superuser"
+                    v-if="$store.state.auth.profile && $store.state.auth.profile.is_superuser"
                     class="ui labeled icon button"
                     :href="$store.getters['instance/absoluteUrl'](`/api/admin/music/library/${object.id}`)"
                     target="_blank" rel="noopener noreferrer">
@@ -38,7 +38,7 @@
                     <i class="dropdown icon"></i>
                     <div class="menu">
                       <a
-                        v-if="$store.state.auth.profile.is_superuser"
+                        v-if="$store.state.auth.profile && $store.state.auth.profile.is_superuser"
                         class="basic item"
                         :href="$store.getters['instance/absoluteUrl'](`/api/admin/music/library/${object.id}`)"
                         target="_blank" rel="noopener noreferrer">
diff --git a/front/src/views/admin/library/TrackDetail.vue b/front/src/views/admin/library/TrackDetail.vue
index 3256d0d639a88a65839186c627533c5c8bd5d499..29cd29810ee619bf96be7ac499b6a5f6d3d5cbcb 100644
--- a/front/src/views/admin/library/TrackDetail.vue
+++ b/front/src/views/admin/library/TrackDetail.vue
@@ -34,7 +34,7 @@
                     <i class="dropdown icon"></i>
                     <div class="menu">
                       <a
-                        v-if="$store.state.auth.profile.is_superuser"
+                        v-if="$store.state.auth.profile && $store.state.auth.profile.is_superuser"
                         class="basic item"
                         :href="$store.getters['instance/absoluteUrl'](`/api/admin/music/track/${object.id}`)"
                         target="_blank" rel="noopener noreferrer">
diff --git a/front/src/views/admin/library/UploadDetail.vue b/front/src/views/admin/library/UploadDetail.vue
index 604d0af3046312605a06e5bf0e1e0b6b400d5954..4dbd83793c5b52374d174417265eae7b5cabcf4b 100644
--- a/front/src/views/admin/library/UploadDetail.vue
+++ b/front/src/views/admin/library/UploadDetail.vue
@@ -28,7 +28,7 @@
 
                 <div class="ui icon buttons">
                   <a
-                    v-if="$store.state.auth.profile.is_superuser"
+                    v-if="$store.state.auth.profile && $store.state.auth.profile.is_superuser"
                     class="ui labeled icon button"
                     :href="$store.getters['instance/absoluteUrl'](`/api/admin/music/upload/${object.id}`)"
                     target="_blank" rel="noopener noreferrer">
@@ -39,7 +39,7 @@
                     <i class="dropdown icon"></i>
                     <div class="menu">
                       <a
-                        v-if="$store.state.auth.profile.is_superuser"
+                        v-if="$store.state.auth.profile && $store.state.auth.profile.is_superuser"
                         class="basic item"
                         :href="$store.getters['instance/absoluteUrl'](`/api/admin/music/upload/${object.id}`)"
                         target="_blank" rel="noopener noreferrer">