diff --git a/api/funkwhale_api/music/filters.py b/api/funkwhale_api/music/filters.py
index 6da9cca63636fd51c562f066c790c473093cffe3..dc7aafc219644ee87c70d0d3e495c9f4b79be04b 100644
--- a/api/funkwhale_api/music/filters.py
+++ b/api/funkwhale_api/music/filters.py
@@ -32,6 +32,33 @@ class ArtistFilter(ListenableMixin):
         }
 
 
+class TrackFilter(filters.FilterSet):
+    q = fields.SearchFilter(search_fields=[
+        'title',
+        'album__title',
+        'artist__name',
+    ])
+    listenable = filters.BooleanFilter(name='_', method='filter_listenable')
+
+    class Meta:
+        model = models.Track
+        fields = {
+            'title': ['exact', 'iexact', 'startswith', 'icontains'],
+            'listenable': ['exact'],
+            'artist': ['exact'],
+            'album': ['exact'],
+        }
+
+    def filter_listenable(self, queryset, name, value):
+        queryset = queryset.annotate(
+            files_count=Count('files')
+        )
+        if value:
+            return queryset.filter(files_count__gt=0)
+        else:
+            return queryset.filter(files_count=0)
+
+
 class ImportBatchFilter(filters.FilterSet):
     q = fields.SearchFilter(search_fields=[
         'submitted_by__username',
@@ -67,7 +94,12 @@ class ImportJobFilter(filters.FilterSet):
 
 class AlbumFilter(ListenableMixin):
     listenable = filters.BooleanFilter(name='_', method='filter_listenable')
+    q = fields.SearchFilter(search_fields=[
+        'title',
+        'artist__name'
+        'source',
+    ])
 
     class Meta:
         model = models.Album
-        fields = ['listenable']
+        fields = ['listenable', 'q', 'artist']
diff --git a/api/funkwhale_api/music/models.py b/api/funkwhale_api/music/models.py
index 51ccec90542affbc408f8019ee6fed0ec31c563b..51ece7d7b3f98b1888139a22c74a5066c1bd7862 100644
--- a/api/funkwhale_api/music/models.py
+++ b/api/funkwhale_api/music/models.py
@@ -319,11 +319,8 @@ class Lyrics(models.Model):
 class TrackQuerySet(models.QuerySet):
     def for_nested_serialization(self):
         return (self.select_related()
-                    .select_related('album__artist')
-                    .prefetch_related(
-                        'tags',
-                        'files',
-                        'artist__albums__tracks__tags'))
+                    .select_related('album__artist', 'artist')
+                    .prefetch_related('files'))
 
 
 class Track(APIModelMixin):
diff --git a/api/funkwhale_api/music/serializers.py b/api/funkwhale_api/music/serializers.py
index 04264692797131d01cdd813c5fa7cd455c0d4d18..c77983a404cf89e9e9f4779ecc3e19773136fda0 100644
--- a/api/funkwhale_api/music/serializers.py
+++ b/api/funkwhale_api/music/serializers.py
@@ -122,7 +122,10 @@ class AlbumSerializer(serializers.ModelSerializer):
         )
 
     def get_tracks(self, o):
-        ordered_tracks = sorted(o.tracks.all(), key=lambda v: v.position)
+        ordered_tracks = sorted(
+            o.tracks.all(),
+            key=lambda v: (v.position, v.title) if v.position else (99999, v.title)
+        )
         return AlbumTrackSerializer(ordered_tracks, many=True).data
 
 
diff --git a/api/funkwhale_api/music/views.py b/api/funkwhale_api/music/views.py
index 3a03a9227a5a4d18e11aafb27d87d5740d54738f..24a9cbbcd0c863f5b5351380d1be5c0c5b0836b0 100644
--- a/api/funkwhale_api/music/views.py
+++ b/api/funkwhale_api/music/views.py
@@ -46,17 +46,6 @@ from . import utils
 logger = logging.getLogger(__name__)
 
 
-class SearchMixin(object):
-    search_fields = []
-
-    @list_route(methods=['get'])
-    def search(self, request, *args, **kwargs):
-        query = utils.get_query(request.GET['query'], self.search_fields)
-        queryset = self.get_queryset().filter(query)
-        serializer = self.serializer_class(queryset, many=True)
-        return Response(serializer.data)
-
-
 class TagViewSetMixin(object):
 
     def get_queryset(self):
@@ -67,26 +56,25 @@ class TagViewSetMixin(object):
         return queryset
 
 
-class ArtistViewSet(SearchMixin, viewsets.ReadOnlyModelViewSet):
+class ArtistViewSet(viewsets.ReadOnlyModelViewSet):
     queryset = models.Artist.objects.with_albums()
     serializer_class = serializers.ArtistWithAlbumsSerializer
     permission_classes = [ConditionalAuthentication]
-    search_fields = ['name__unaccent']
     filter_class = filters.ArtistFilter
     ordering_fields = ('id', 'name', 'creation_date')
 
 
-class AlbumViewSet(SearchMixin, viewsets.ReadOnlyModelViewSet):
+class AlbumViewSet(viewsets.ReadOnlyModelViewSet):
     queryset = (
         models.Album.objects.all()
-                            .order_by('-creation_date')
+                            .order_by('artist', 'release_date')
                             .select_related()
-                            .prefetch_related('tracks__tags',
-                                              'tracks__files'))
+                            .prefetch_related(
+                                'tracks__artist',
+                                'tracks__files'))
     serializer_class = serializers.AlbumSerializer
     permission_classes = [ConditionalAuthentication]
-    search_fields = ['title__unaccent']
-    ordering_fields = ('creation_date',)
+    ordering_fields = ('creation_date', 'release_date', 'title')
     filter_class = filters.AlbumFilter
 
 
@@ -155,19 +143,20 @@ class ImportJobViewSet(
         )
 
 
-class TrackViewSet(
-        TagViewSetMixin, SearchMixin, viewsets.ReadOnlyModelViewSet):
+class TrackViewSet(TagViewSetMixin, viewsets.ReadOnlyModelViewSet):
     """
     A simple ViewSet for viewing and editing accounts.
     """
     queryset = (models.Track.objects.all().for_nested_serialization())
     serializer_class = serializers.TrackSerializer
     permission_classes = [ConditionalAuthentication]
-    search_fields = ['title', 'artist__name']
+    filter_class = filters.TrackFilter
     ordering_fields = (
         'creation_date',
         'title__unaccent',
         'album__title__unaccent',
+        'album__release_date',
+        'position',
         'artist__name__unaccent',
     )
 
diff --git a/api/funkwhale_api/radios/filters.py b/api/funkwhale_api/radios/filters.py
index 344a4dabff3fb29b1d0fc7d5b8f4bda480c786ae..d0d338d663a62008ce6f1ccf03e3e4f8ed679b37 100644
--- a/api/funkwhale_api/radios/filters.py
+++ b/api/funkwhale_api/radios/filters.py
@@ -144,8 +144,8 @@ class ArtistFilter(RadioFilter):
             'name': 'ids',
             'type': 'list',
             'subtype': 'number',
-            'autocomplete': reverse_lazy('api:v1:artists-search'),
-            'autocomplete_qs': 'query={query}',
+            'autocomplete': reverse_lazy('api:v1:artists-list'),
+            'autocomplete_qs': 'q={query}',
             'autocomplete_fields': {'name': 'name', 'value': 'id'},
             'label': 'Artist',
             'placeholder': 'Select artists'
diff --git a/api/funkwhale_api/requests/views.py b/api/funkwhale_api/requests/views.py
index 395fac66cff33e3a01fdcc33a56adfcea0aa0c7e..6553f3316fb3cbd50f040202d0bb36a38a7fceb6 100644
--- a/api/funkwhale_api/requests/views.py
+++ b/api/funkwhale_api/requests/views.py
@@ -3,15 +3,12 @@ from rest_framework import status
 from rest_framework.response import Response
 from rest_framework.decorators import detail_route
 
-from funkwhale_api.music.views import SearchMixin
-
 from . import filters
 from . import models
 from . import serializers
 
 
 class ImportRequestViewSet(
-        SearchMixin,
         mixins.CreateModelMixin,
         mixins.RetrieveModelMixin,
         mixins.ListModelMixin,
@@ -22,7 +19,6 @@ class ImportRequestViewSet(
         models.ImportRequest.objects.all()
               .select_related()
               .order_by('-creation_date'))
-    search_fields = ['artist_name', 'album_name', 'comment']
     filter_class = filters.ImportRequestFilter
     ordering_fields = ('id', 'artist_name', 'creation_date', 'status')
 
diff --git a/front/src/components/audio/PlayButton.vue b/front/src/components/audio/PlayButton.vue
index efa59a29d5c65fe30b88aedc5f453b8e9a250a61..6fc7699690f53ab66e998d320655327f51d21cc9 100644
--- a/front/src/components/audio/PlayButton.vue
+++ b/front/src/components/audio/PlayButton.vue
@@ -21,7 +21,6 @@
 
 <script>
 import axios from 'axios'
-import logger from '@/logging'
 import jQuery from 'jquery'
 
 export default {
@@ -30,18 +29,15 @@ export default {
     tracks: {type: Array, required: false},
     track: {type: Object, required: false},
     playlist: {type: Object, required: false},
-    discrete: {type: Boolean, default: false}
+    discrete: {type: Boolean, default: false},
+    artist: {type: Number, required: false},
+    album: {type: Number, required: false}
   },
   data () {
     return {
       isLoading: false
     }
   },
-  created () {
-    if (!this.playlist && !this.track && !this.tracks) {
-      logger.default.error('You have to provide either a track playlist or tracks property')
-    }
-  },
   mounted () {
     jQuery(this.$el).find('.ui.dropdown').dropdown()
   },
@@ -62,6 +58,10 @@ export default {
         return this.tracks.length > 0
       } else if (this.playlist) {
         return true
+      } else if (this.artist) {
+        return true
+      } else if (this.album) {
+        return true
       }
       return false
     }
@@ -81,6 +81,20 @@ export default {
               return plt.track
             }))
           })
+        } else if (self.artist) {
+          let params = {
+            params: {'artist': self.artist, 'ordering': 'album__release_date,position'}
+          }
+          axios.get('tracks', params).then((response) => {
+            resolve(response.data.results)
+          })
+        } else if (self.album) {
+          let params = {
+            params: {'album': self.album, 'ordering': 'position'}
+          }
+          axios.get('tracks', params).then((response) => {
+            resolve(response.data.results)
+          })
         }
       })
       return getTracks.then((tracks) => {
diff --git a/front/src/components/audio/artist/Card.vue b/front/src/components/audio/artist/Card.vue
index 3ad6fb1c60263ac2d350350b3318eecec72885d0..a46506791e083eb5cc750c8f84918a6bc1fb2318 100644
--- a/front/src/components/audio/artist/Card.vue
+++ b/front/src/components/audio/artist/Card.vue
@@ -18,10 +18,10 @@
                   <router-link class="discrete link":to="{name: 'library.albums.detail', params: {id: album.id }}">
                     <strong>{{ album.title }}</strong>
                   </router-link><br />
-                  {{ album.tracks.length }} tracks
+                  {{ album.tracks_count }} tracks
                 </td>
                 <td>
-                  <play-button class="right floated basic icon" :discrete="true" :tracks="album.tracks"></play-button>
+                  <play-button class="right floated basic icon" :discrete="true" :album="album.id"></play-button>
                 </td>
               </tr>
             </tbody>
@@ -45,7 +45,7 @@
             {{ artist.albums.length }}
           </i18next>
         </span>
-        <play-button class="mini basic orange right floated" :tracks="allTracks">
+        <play-button class="mini basic orange right floated" :artist="artist.id">
           <i18next path="Play all"/>
         </play-button>
       </div>
@@ -74,15 +74,6 @@ export default {
         return this.artist.albums
       }
       return this.artist.albums.slice(0, this.initialAlbums)
-    },
-    allTracks () {
-      let tracks = []
-      this.artist.albums.forEach(album => {
-        album.tracks.forEach(track => {
-          tracks.push(track)
-        })
-      })
-      return tracks
     }
   }
 }
diff --git a/front/src/components/library/Artist.vue b/front/src/components/library/Artist.vue
index e16cb6587431d0d7494cae1ef0e92bf5c96fe189..7d0a41d8988055316ecb4773103e129e970f92c7 100644
--- a/front/src/components/library/Artist.vue
+++ b/front/src/components/library/Artist.vue
@@ -10,7 +10,7 @@
             <i class="circular inverted users violet icon"></i>
             <div class="content">
               {{ artist.name }}
-              <div class="sub header">
+              <div class="sub header" v-if="albums">
                 {{ $t('{% track_count %} tracks in {% album_count %} albums', {track_count: totalTracks, album_count: albums.length})}}
               </div>
             </div>
@@ -18,7 +18,7 @@
           <div class="ui hidden divider"></div>
           <radio-button type="artist" :object-id="artist.id"></radio-button>
           </button>
-          <play-button class="orange" :tracks="allTracks"><i18next path="Play all albums"/></play-button>
+          <play-button class="orange" :artist="artist.id"><i18next path="Play all albums"/></play-button>
 
           <a :href="wikipediaUrl" target="_blank" class="ui button">
             <i class="wikipedia icon"></i>
@@ -30,10 +30,13 @@
           </a>
         </div>
       </div>
-      <div class="ui vertical stripe segment">
+      <div v-if="isLoadingAlbums" class="ui vertical stripe segment">
+        <div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
+      </div>
+      <div v-else-if="albums" class="ui vertical stripe segment">
         <h2><i18next path="Albums by this artist"/></h2>
         <div class="ui stackable doubling three column grid">
-          <div class="column" :key="album.id" v-for="album in sortedAlbums">
+          <div class="column" :key="album.id" v-for="album in albums">
             <album-card :mode="'rich'" class="fluid" :album="album"></album-card>
           </div>
         </div>
@@ -43,7 +46,6 @@
 </template>
 
 <script>
-import _ from 'lodash'
 import axios from 'axios'
 import logger from '@/logging'
 import backend from '@/audio/backend'
@@ -63,6 +65,7 @@ export default {
   data () {
     return {
       isLoading: true,
+      isLoadingAlbums: true,
       artist: null,
       albums: null
     }
@@ -78,18 +81,19 @@ export default {
       logger.default.debug('Fetching artist "' + this.id + '"')
       axios.get(url).then((response) => {
         self.artist = response.data
-        self.albums = JSON.parse(JSON.stringify(self.artist.albums)).map((album) => {
-          return backend.Album.clean(album)
-        })
         self.isLoading = false
+        self.isLoadingAlbums = true
+        axios.get('albums/', {params: {artist: this.id, ordering: '-release_date'}}).then((response) => {
+          self.albums = JSON.parse(JSON.stringify(response.data.results)).map((album) => {
+            return backend.Album.clean(album)
+          })
+
+          self.isLoadingAlbums = false
+        })
       })
     }
   },
   computed: {
-    sortedAlbums () {
-      let a = this.albums || []
-      return _.orderBy(a, ['release_date'], ['asc'])
-    },
     totalTracks () {
       return this.albums.map((album) => {
         return album.tracks.length
diff --git a/front/src/components/library/Artists.vue b/front/src/components/library/Artists.vue
index 0811f0aa79083da03b5c12b74795ef9ddefb7c8f..aafa3a160e6798729bb0d7652d0c1f2b7304db94 100644
--- a/front/src/components/library/Artists.vue
+++ b/front/src/components/library/Artists.vue
@@ -69,7 +69,6 @@ import axios from 'axios'
 import _ from 'lodash'
 import $ from 'jquery'
 
-import backend from '@/audio/backend'
 import logger from '@/logging'
 
 import OrderingMixin from '@/components/mixins/Ordering'
@@ -135,13 +134,6 @@ export default {
       logger.default.debug('Fetching artists')
       axios.get(url, {params: params}).then((response) => {
         self.result = response.data
-        self.result.results.map((artist) => {
-          var albums = JSON.parse(JSON.stringify(artist.albums)).map((album) => {
-            return backend.Album.clean(album)
-          })
-          artist.albums = albums
-          return artist
-        })
         self.isLoading = false
       })
     }, 500),
diff --git a/front/src/components/library/Home.vue b/front/src/components/library/Home.vue
index e69ecea805c8fda726d3c02e50e46dc93d881c41..2be693fab2ed7a2a58934b36945f21ec8ca4e41d 100644
--- a/front/src/components/library/Home.vue
+++ b/front/src/components/library/Home.vue
@@ -30,7 +30,6 @@
 <script>
 import axios from 'axios'
 import Search from '@/components/audio/Search'
-import backend from '@/audio/backend'
 import logger from '@/logging'
 import ArtistCard from '@/components/audio/artist/Card'
 import RadioCard from '@/components/radios/Card'
@@ -66,13 +65,6 @@ export default {
       logger.default.time('Loading latest artists')
       axios.get(url, {params: params}).then((response) => {
         self.artists = response.data.results
-        self.artists.map((artist) => {
-          var albums = JSON.parse(JSON.stringify(artist.albums)).map((album) => {
-            return backend.Album.clean(album)
-          })
-          artist.albums = albums
-          return artist
-        })
         logger.default.timeEnd('Loading latest artists')
         self.isLoadingArtists = false
       })
diff --git a/front/src/components/library/radios/Builder.vue b/front/src/components/library/radios/Builder.vue
index 85642b0a1235a61b859934df1fd46e5a2943dfc2..6c81f532f2a9478ca7a66ecfbeb255e103ba2c09 100644
--- a/front/src/components/library/radios/Builder.vue
+++ b/front/src/components/library/radios/Builder.vue
@@ -14,7 +14,7 @@
               <input id="public" type="checkbox" v-model="isPublic" />
               <i18next tag="label" for="public" path="Display publicly"/>
             </div>
-            <button :disabled="!canSave" @click="save" class="ui green button"><i18ext path="Save"/></button>
+            <button :disabled="!canSave" @click="save" class="ui green button"><i18next path="Save"/></button>
             <radio-button v-if="id" type="custom" :custom-radio-id="id"></radio-button>
           </div>
         </div>
diff --git a/front/src/components/library/radios/Filter.vue b/front/src/components/library/radios/Filter.vue
index c7f981ede934084606b07d068be5f88a9fdfa9c9..b27c36077c113b801bf842f84ad868cfecc87542 100644
--- a/front/src/components/library/radios/Filter.vue
+++ b/front/src/components/library/radios/Filter.vue
@@ -123,7 +123,7 @@ export default {
             if (settings.fields.remoteValues) {
               return initialResponse
             }
-            return {results: initialResponse}
+            return {results: initialResponse.results}
           }
         }
       }