diff --git a/src/client/api.ts b/src/client/api.ts
index c66b9ef9fa78924efb9f1d6cbb4ad6c8232b169a..ca2db43e8496af498b7901b333ec183c2f757987 100644
--- a/src/client/api.ts
+++ b/src/client/api.ts
@@ -7,7 +7,6 @@ import type {
   APIDownloadResponse,
   APIFetchParams,
   APIFetchResponse,
-  APISegmentsResponse,
   APIUserMadeArchiveResponse,
   Antibody,
   Statistics
@@ -40,15 +39,6 @@ export default Object.freeze({
   downloadAntibodies: async (params: APIDownloadParams): Promise<AxiosResponse<APIDownloadResponse>> => {
     return await base.get('/api/antibodies/download', { params })
   },
-  getAntibodiesSources: async (): Promise<AxiosResponse<string[]>> => {
-    return await base.get('/api/antibodies/sources')
-  },
-  getAntibodiesSpecies: async (): Promise<AxiosResponse<string[]>> => {
-    return await base.get('/api/antibodies/species')
-  },
-  getAntibodiesSegments: async (): Promise<AxiosResponse<APISegmentsResponse>> => {
-    return await base.get('/api/antibodies/v-gene-segments')
-  },
   getStatistics: async (): Promise<AxiosResponse<Statistics>> => {
     return await base.get('/api/statistics')
   },
diff --git a/src/client/components/AdvancedSearch.vue b/src/client/components/AdvancedSearch.vue
index 21a3c6a2ad4e00da2c819ae687b7a2381fd85ff7..c4e204e51dfbd1c3c43537010218025f76a555e5 100644
--- a/src/client/components/AdvancedSearch.vue
+++ b/src/client/components/AdvancedSearch.vue
@@ -168,6 +168,7 @@
       <button
         class="search-button"
         type="button"
+        :disabled="isFetchingAntibodies"
         @click="closeAdvancedSearch"
       >
         SEARCH
@@ -184,20 +185,13 @@ import AppCheckbox from './AppCheckbox.vue'
 import AppDialog from './AppDialog.vue'
 import MotifInput from './MotifInput.vue'
 
-// onMounted(() => {
-//   if (store.antibodiesSources.length === 0) {
-//     store.getAntibodiesSources()
-//     store.getAntibodiesSpecies()
-//     store.getAntibodiesSegments()
-//   }
-// })
-
 const emit = defineEmits<{
   filtersUpdate: [filters: AdvancedFilters]
 }>()
 
 const store = useStore()
 const request = computed(() => store.request)
+const isFetchingAntibodies = computed(() => { return store.isFetchingAntibodies })
 const advancedSearchVisible = ref(false)
 const filters = ref(request.value.advancedFilters())
 const motifIsValid = ref(true)
diff --git a/src/client/components/AntibodiesTable.vue b/src/client/components/AntibodiesTable.vue
index efded9edb954a0ad0a93b30f119fa25d67a2b812..2d02d4fcf853cc952f4220c79486699365b22231 100644
--- a/src/client/components/AntibodiesTable.vue
+++ b/src/client/components/AntibodiesTable.vue
@@ -68,6 +68,7 @@
           </th>
         </tr>
       </thead>
+
       <tbody>
         <tr
           v-for="antibody in antibodies"
@@ -78,7 +79,8 @@
             data-label="ID"
             class="link-cell"
           >
-            <RouterLink :to="{ name: 'AntibodyPage', params: { hashId: antibody.hashId } }">
+            <span class="loader" v-if="isFetchingAntibodies"></span>
+            <RouterLink v-else :to="{ name: 'AntibodyPage', params: { hashId: antibody.hashId } }">
               {{ antibody.id }}
             </RouterLink>
           </td>
@@ -86,25 +88,29 @@
             headers="antibodies-species"
             data-label="SPECIES"
           >
-            <i>{{ antibody.species }}</i>
+            <span class="loader" v-if="isFetchingAntibodies"></span>
+            <i v-else>{{ antibody.species }}</i>
           </td>
           <td
             headers="antibodies-heavy-chain"
             data-label="HEAVY CHAIN"
           >
-            {{ antibody.heavyChain.sequence.substring(0, 15) }}...
+            <span class="loader" v-if="isFetchingAntibodies"></span>
+            <span v-else>{{ antibody.heavyChain.sequence.substring(0, 15) }}...</span>
           </td>
           <td
             headers="antibodies-light-chain"
             data-label="LIGHT CHAIN"
           >
-            {{ antibody.lightChain.sequence.substring(0, 15) }}...
+            <span class="loader" v-if="isFetchingAntibodies"></span>
+            <span v-else>{{ antibody.lightChain.sequence.substring(0, 15) }}...</span>
           </td>
           <td
             headers="antibodies-sources"
             data-label="SOURCES"
           >
-            {{ listSources(antibody).join(', ') }}
+            <span class="loader" v-if="isFetchingAntibodies"></span>
+            <span v-else>{{ listSources(antibody).join(', ') }}</span>
           </td>
         </tr>
       </tbody>
@@ -124,6 +130,7 @@ import AntibodiesTableTotal from './AntibodiesTableTotal.vue';
 
 const store = useStore()
 const antibodies = computed(() => { return store.antibodies })
+const isFetchingAntibodies = computed(() => { return store.isFetchingAntibodies })
 
 /**
  * Sort the table by refetching the results and make the sort
@@ -132,6 +139,8 @@ const antibodies = computed(() => { return store.antibodies })
  * @param key - The antibody key corresponding to the column to sort by
  */
 function sortBy(key: string): void {
+  if (isFetchingAntibodies.value) return
+
   if (store.request.sort.by === key) {
     store.request.sort.order = store.request.sort.order === 'asc' ? 'desc' : 'asc'
     store.fetchAntibodies(store.request.fetchParams())
@@ -191,10 +200,11 @@ function listSources(antibody: Antibody): string[] {
 
 .link-cell a {
   display: inline-block;
+  max-width: 200px;
   overflow: hidden;
   text-overflow: ellipsis;
+  vertical-align: middle;
   white-space: nowrap;
-  max-width: 200px;
 }
 
 @media screen and (width < 700px) {
diff --git a/src/client/components/AntibodiesTableDownload.vue b/src/client/components/AntibodiesTableDownload.vue
index da2356085a51fd511f05bc8589b8b1ccb9811703..9d41c1d9559c1ce0e56fdb9f1b2f8e984a0aaef8 100644
--- a/src/client/components/AntibodiesTableDownload.vue
+++ b/src/client/components/AntibodiesTableDownload.vue
@@ -3,7 +3,7 @@
     <button
       class="download-button"
       type="button"
-      :disabled="isDownloading"
+      :disabled="isFetchingAntibodies"
       @click="download"
     >
       <span class="download-button-text">
@@ -48,6 +48,7 @@ const store = useStore()
 const request = computed(() => { return store.request })
 const antibodiesCount = computed(() => { return store.antibodiesCount })
 const isDownloading = computed(() => { return store.isDownloading })
+const isFetchingAntibodies = computed(() => { return store.isFetchingAntibodies })
 const isFetchingCount = computed(() => { return store.isFetchingCount })
 const downloadFormat: Ref<APIDownloadFormat> = ref('fasta')
 
diff --git a/src/client/components/AntibodiesTablePagination.vue b/src/client/components/AntibodiesTablePagination.vue
index 5431d30b5abebc6c5bab370666eb7e4e51fdcdf5..51299cc2ba7c0ca02099cca1f5b8843905416474 100644
--- a/src/client/components/AntibodiesTablePagination.vue
+++ b/src/client/components/AntibodiesTablePagination.vue
@@ -3,6 +3,7 @@
     <button
       class="previous-button"
       type="button"
+      :disabled="isFetchingAntibodies"
       @click="getPrevious"
     >
       &#60;
@@ -11,6 +12,7 @@
     <button
       class="next-button"
       type="button"
+      :disabled="isFetchingAntibodies"
       @click="getNext"
     >
       &#62;
@@ -30,6 +32,9 @@ const antibodiesCount = computed(() => { return store.antibodiesCount })
 const pagesCount = computed(() => {
   return Math.ceil(antibodiesCount.value / rowsPerPage)
 })
+const isFetchingAntibodies = computed(() => {
+  return store.isFetchingAntibodies
+})
 
 /**
  * Reset the pagination if the request has changed.
diff --git a/src/client/components/SearchBar.vue b/src/client/components/SearchBar.vue
index ef97a3f9359a2882050d986defc71052023f93a0..bb062c6c83448f4598a6df73a003dbb2c0534153 100644
--- a/src/client/components/SearchBar.vue
+++ b/src/client/components/SearchBar.vue
@@ -18,6 +18,7 @@
       <button
         class="search-button"
         type="button"
+        :disabled="isFetchingAntibodies"
         @click="fetchAntibodies">
         SEARCH
       </button>
@@ -28,11 +29,13 @@
 </template>
 
 <script setup lang="ts">
+import { computed } from 'vue'
 import { useStore } from '../store'
 import type { AdvancedFilters } from '../types'
 import AdvancedSearch from './AdvancedSearch.vue'
 
 const store = useStore()
+const isFetchingAntibodies = computed(() => { return store.isFetchingAntibodies })
 
 /**
  * When the advanced filters are changed, store them locally and
diff --git a/src/client/components/TheDownloadPage.vue b/src/client/components/TheDownloadPage.vue
index 4334bf80ce75c913cc2e9228e890217db3c72629..8467f5b18f93a164a589f49fd7aab8916a727c2d 100644
--- a/src/client/components/TheDownloadPage.vue
+++ b/src/client/components/TheDownloadPage.vue
@@ -32,18 +32,34 @@ const props = defineProps<{
   archiveId: string
 }>()
 
+/** The link to the archive when its ready */
 const archiveLink = ref<string>()
+
+/** ID of the interval timer started on page load */
 let intervalID: number
 
+/**
+ * On page load, start a timer to fetch the file status
+ * every 3 seconds.
+ */
 onMounted(() => {
   getFileLink()
-  intervalID = setInterval(getFileLink, 2000)
+  intervalID = setInterval(getFileLink, 3000)
 })
 
+/**
+ * Remove the timer when leaving the page to ensure
+ * memory safety.
+ */
 onUnmounted(() => {
   clearInterval(intervalID)
 })
 
+/**
+ * Get the status of the building archive and get
+ * the link to if its ready.
+ * Used with the interval timer.
+ */
 function getFileLink() {
   api.fetchUserMadeArchive(props.archiveId).then((response) => {
     if (response.status === 202) return
diff --git a/src/client/components/TheHomePage.vue b/src/client/components/TheHomePage.vue
index 640b4a4692ff12b494080a7c0842be35ef9e8809..e575d38c33580549caaffa245e49d65dd46208df 100644
--- a/src/client/components/TheHomePage.vue
+++ b/src/client/components/TheHomePage.vue
@@ -126,7 +126,7 @@
 
   <SearchBar />
 
-  <AntibodiesTable v-if="antibodies.length && !noResults" />
+  <AntibodiesTable v-if="!noResults" />
 
   <NoResults v-if="noResults" />
 </template>
@@ -139,7 +139,6 @@ import NoResults from './NoResults.vue'
 import SearchBar from './SearchBar.vue'
 
 const store = useStore()
-const antibodies = computed(() => { return store.antibodies })
 const noResults = computed(() => { return store.noResults })
 
 /**
diff --git a/src/client/globals.css b/src/client/globals.css
index c7c81b04ae7883a831ce8b62632abae7a6f52f3c..2cd5a8a528d8157db676ab2a8f54797d9d59d8a2 100644
--- a/src/client/globals.css
+++ b/src/client/globals.css
@@ -43,6 +43,7 @@
   --black: hsl(0, 0%, 4%);
   --black-translucent: hsla(0, 0%, 4%, 0.5);
   --grey: hsl(0, 0%, 90%);
+  --grey-translucent: hsl(0, 0%, 90%, 0.5);
   --white: hsl(0, 0%, 100%);
 
   --spacing: 20px;
@@ -106,17 +107,20 @@ button {
   padding: var(--button-padding);
   transition: background-color 0.1s;
 
-  &:hover {
-    border-color: var(--primary);
-    color: var(--primary);
-  }
-
   &:active {
     background-color: var(--primary-translucent);
   }
 
+  &:hover:enabled {
+    border-color: var(--primary);
+    color: var(--primary);
+  }
+
   &:disabled {
     cursor: not-allowed;
+    color: var(--grey);
+    background-color: var(--grey-translucent);
+    border-color: var(--grey);
   }
 }
 
@@ -135,13 +139,13 @@ i {
   font-weight: bold;
   padding: var(--half-spacing) var(--spacing);
 
-  &:hover {
+  &:hover:enabled {
     background-color: transparent;
     border-color: var(--primary);
     color: var(--primary);
   }
 
-  &:active {
+  &:active:enabled {
     background-color: var(--primary);
     color: var(--black);
   }
@@ -167,3 +171,32 @@ i {
     transform: rotate(360deg);
   }
 }
+
+.loader {
+  width: 200px;
+  height: 18px;
+  display: inline-block;
+  background-color: var(--primary-translucent);
+  border-radius: var(--radius);
+  background-image: linear-gradient(
+    45deg,
+    var(--primary) 25%,
+    transparent 25%,
+    transparent 50%,
+    var(--primary) 50%,
+    var(--primary) 75%,
+    transparent 75%
+  );
+  background-size: 1em 1em;
+  box-sizing: border-box;
+  animation: bar-stripe 0.5s linear infinite;
+}
+
+@keyframes bar-stripe {
+  0% {
+    background-position: 1em 0;
+  }
+  100% {
+    background-position: 0 0;
+  }
+}
diff --git a/src/client/store.ts b/src/client/store.ts
index 948e4eca85d68f3b2047742266db4b5d94100af7..149d0f742285bbeaa9c091dbc906e416b299eb3b 100644
--- a/src/client/store.ts
+++ b/src/client/store.ts
@@ -20,13 +20,10 @@ export const useStore = defineStore('store', {
     return {
       antibodies: [],
       antibodiesCount: 0,
-      antibodiesSources: [],
-      antibodiesSpecies: [],
-      antibodiesHeavySegments: [],
-      antibodiesLightSegments: [],
       archives: [],
       statistics: null,
       isDownloading: false,
+      isFetchingAntibodies: false,
       isFetchingCount: false,
       request: new APIRequest(),
       noResults: false,
@@ -34,34 +31,74 @@ export const useStore = defineStore('store', {
     }
   },
   getters: {
+    /**
+     * Get the current list of sources from the statistics.
+     * @param state - The state of the store
+     * @returns The list of sources from the stats
+     */
     antibodiesSources: (state): Statistics['sources'] => {
       return state.statistics?.sources || []
     },
+
+    /**
+     * Get the current list of species from the statistics.
+     * @param state - The state of the store
+     * @returns The list of species from the stats
+     */
     antibodiesSpecies: (state): Statistics['species'] => {
       return state.statistics?.species || []
     },
-    antibodiesHeavySegments: (state) => {
+
+    /**
+     * Get the current list of heavy segments from the statistics.
+     * @param state - The state of the store
+     * @returns The list of heavy segments from the stats
+     */
+    antibodiesHeavySegments: (state): Array<keyof Statistics['antibodiesPerHeavySegment']> => {
       return Object
         .keys(state.statistics?.antibodiesPerHeavySegment || {})
         .sort(alphanumSort)
     },
-    antibodiesLightSegments: (state) => {
+
+    /**
+     * Get the current list of light segments from the statistics.
+     * @param state - The state of the store
+     * @returns The list of light segments from the stats
+     */
+    antibodiesLightSegments: (state): Array<keyof Statistics['antibodiesPerHeavySegment']> => {
       return Object
         .keys(state.statistics?.antibodiesPerLightSegment || {})
         .sort(alphanumSort)
     }
   },
   actions: {
+    /**
+     * The main action for fetching antibodies from the server,
+     * used inside components.
+     * Stores the results in the corresponding store variable
+     * instead of returning anything.
+     * @param request - The request parameters for getting antibodies
+     */
     fetchAntibodies(request: APIFetchParams): void {
+      this.isFetchingAntibodies = true
       api.fetchAntibodies(request).then(response => {
         this.noResults = response.data.length === 0
         this.antibodies = response.data
       }).catch((err: AxiosError) => {
         alert(err.message)
         console.log(err)
+      }).finally(() => {
+        this.isFetchingAntibodies = false
       })
     },
 
+    /**
+     * The main action to count the total number of antibodies
+     * matching a request.
+     * Stores the results in the corresponding store variable
+     * instead of returning anything.
+     * @param countRequest - The request parameters for counting
+     */
     countAntibodies(countRequest: APICountParams): void {
       // Avoid to send an empty request to get an information
       // we already have in the stats.
@@ -81,6 +118,12 @@ export const useStore = defineStore('store', {
       })
     },
 
+    /**
+     * Action to trigger the building of an archive by the server
+     * in order to download it.
+     * Re-route to the download waiting page.
+     * @param request - The request parameters to filter antibodies and set the archive format
+     */
     downloadAntibodies(request: APIDownloadParams): void {
       this.isDownloading = true
       api.downloadAntibodies(request).then(response => {
@@ -98,6 +141,10 @@ export const useStore = defineStore('store', {
       })
     },
 
+    /**
+     * Get the database statistics from the server and stores it in the
+     * corresponding variable in the store.
+     */
     getStatistics(): void {
       api.getStatistics().then(response => {
         this.statistics = Object.freeze(response.data)
@@ -107,12 +154,23 @@ export const useStore = defineStore('store', {
       })
     },
 
+    /**
+     * Build a link to an antibody external resource using the template
+     * in the sources metadata. Those can be found in the `data/sources.json` file.
+     * @param id - One of the antibody ID found in its FASTA headers
+     * @param source - The name of the source
+     * @returns The corresponding link, usually a url.
+     */
     getSourceLink(id: Antibody['id'], source: FastaHeader['source']): string {
       const link = this.sourceMeta[source]?.link
       if (link === undefined) return ''
       return link.replace(/{id}/g, id)
     },
 
+    /**
+     * Fetch the list of pre-made archives from the server
+     * and stores it in the store.
+     */
     fetchArchives(): void {
       api.fetchArchives().then(response => {
         this.archives = response.data
diff --git a/src/client/types.ts b/src/client/types.ts
index 18b8e34532322ba7fd595b58476bc4bfbbd6314e..b04bbfd1beb492f8ef899512e66dc3e3f3f73006 100644
--- a/src/client/types.ts
+++ b/src/client/types.ts
@@ -9,13 +9,10 @@ import type { APIRequest } from './models/APIRequest'
 export interface AppState {
   antibodies: Antibody[]
   antibodiesCount: number
-  antibodiesSources: Readonly<Array<FastaHeader['source']>>
-  antibodiesSpecies: Readonly<Array<Antibody['species']>>
-  antibodiesHeavySegments: Readonly<Array<FastaHeader['vGeneSegment']>>
-  antibodiesLightSegments: Readonly<Array<FastaHeader['vGeneSegment']>>
   archives: Array<string>
   statistics: Readonly<Statistics> | null
   isDownloading: boolean
+  isFetchingAntibodies: boolean
   isFetchingCount: boolean
   noResults: boolean
   request: APIRequest
@@ -72,8 +69,8 @@ export interface Statistics {
   antibodiesPerSource: Record<FastaHeader['source'], number>,
   antibodiesOnlyInSource: Record<FastaHeader['source'], number>
   antibodiesInMultipleSources: Record<string, number>
-  antibodiesPerHeavySegment: Record<string, number>
-  antibodiesPerLightSegment: Record<string, number>
+  antibodiesPerHeavySegment: Record<FastaHeader['vGeneSegment'], number>
+  antibodiesPerLightSegment: Record<FastaHeader['vGeneSegment'], number>
 }
 
 /**
@@ -131,15 +128,6 @@ export interface APICountResponse {
   count: number
 }
 
-/**
- * The response from the server when fetching the lists
- * of unique V gene segments present in the database.
- */
-export interface APISegmentsResponse {
-  heavySegments: Array<FastaHeader['vGeneSegment']>
-  lightSegments: Array<FastaHeader['vGeneSegment']>
-}
-
 /**
  * The response from the server when requesting a download
  * of the main table data. It only returns the name of the
diff --git a/src/server/routes/statisticsRoute.js b/src/server/routes/statisticsRoute.js
index eb033bcdf0944c0bd496d6f75df5e54b1989d0a1..ebf884c74f9c29cb27881abe92350474b87d5873 100644
--- a/src/server/routes/statisticsRoute.js
+++ b/src/server/routes/statisticsRoute.js
@@ -23,7 +23,9 @@ export default {
   handler: function (request, reply) {
     this.mongo.db
       .collection('statistics')
-      .findOne()
+      .findOne({}, {
+        projection: { _id: false }
+      })
       .then(stats => {
         return reply
           .code(200)