diff --git a/js/package.json b/js/package.json
index 0bfcdb343..cabd2a04d 100644
--- a/js/package.json
+++ b/js/package.json
@@ -30,7 +30,6 @@
     "graphql": "^15.0.0",
     "graphql-tag": "^2.10.3",
     "intersection-observer": "^0.11.0",
-    "javascript-time-ago": "^2.0.4",
     "leaflet": "^1.4.0",
     "leaflet.locatecontrol": "^0.72.0",
     "lodash": "^4.17.11",
@@ -54,7 +53,6 @@
   },
   "devDependencies": {
     "@types/chai": "^4.2.11",
-    "@types/javascript-time-ago": "^2.0.1",
     "@types/leaflet": "^1.5.2",
     "@types/leaflet.locatecontrol": "^0.60.7",
     "@types/lodash": "^4.14.141",
diff --git a/js/src/common.scss b/js/src/common.scss
index 2178b6398..cf93afa73 100644
--- a/js/src/common.scss
+++ b/js/src/common.scss
@@ -1,5 +1,9 @@
 @import "variables.scss";
 
+@import "~bulma";
+@import "~bulma-divider";
+@import "~buefy/src/scss/buefy";
+
 // a {
 //   color: $violet-2;
 // }
diff --git a/js/src/components/Account/ActorCard.vue b/js/src/components/Account/ActorCard.vue
index 4f910e534..0141a639a 100644
--- a/js/src/components/Account/ActorCard.vue
+++ b/js/src/components/Account/ActorCard.vue
@@ -46,8 +46,6 @@ export default class ActorCard extends Vue {
 </style>
 
 <style lang="scss">
-@import "../../variables.scss";
-
 .tooltip {
   display: block !important;
   z-index: 10000;
diff --git a/js/src/components/Account/ParticipantCard.vue b/js/src/components/Account/ParticipantCard.vue
index 8816e85bd..5284361d9 100644
--- a/js/src/components/Account/ParticipantCard.vue
+++ b/js/src/components/Account/ParticipantCard.vue
@@ -88,7 +88,6 @@ export default class ParticipantCard extends Vue {
 </script>
 
 <style lang="scss">
-@import "../../variables.scss";
 .card-footer-item {
   height: $control-height;
 }
diff --git a/js/src/components/Admin/Followers.vue b/js/src/components/Admin/Followers.vue
index 73d65c186..82962e7ee 100644
--- a/js/src/components/Admin/Followers.vue
+++ b/js/src/components/Admin/Followers.vue
@@ -51,7 +51,7 @@
 
       <b-table-column field="targetActor.updatedAt" :label="$t('Date')" sortable v-slot="props">
         <span :title="$options.filters.formatDateTimeString(props.row.updatedAt)">{{
-          timeago(props.row.updatedAt)
+          formatDistanceToNow(new Date(props.row.updatedAt), { locale: $dateFnsLocale })
         }}</span></b-table-column
       >
 
@@ -102,6 +102,7 @@
 <script lang="ts">
 import { Component, Mixins } from "vue-property-decorator";
 import { SnackbarProgrammatic as Snackbar } from "buefy";
+import { formatDistanceToNow } from "date-fns";
 import { ACCEPT_RELAY, REJECT_RELAY, RELAY_FOLLOWERS } from "../../graphql/admin";
 import { Paginate } from "../../types/paginate";
 import { IFollower } from "../../types/actor/follower.model";
@@ -126,6 +127,8 @@ export default class Followers extends Mixins(RelayMixin) {
 
   RelayMixin = RelayMixin;
 
+  formatDistanceToNow = formatDistanceToNow;
+
   async acceptRelays(): Promise<void> {
     await this.checkedRows.forEach((row: IFollower) => {
       this.acceptRelay(`${row.actor.preferredUsername}@${row.actor.domain}`);
diff --git a/js/src/components/Admin/Followings.vue b/js/src/components/Admin/Followings.vue
index 031839500..79bcff133 100644
--- a/js/src/components/Admin/Followings.vue
+++ b/js/src/components/Admin/Followings.vue
@@ -64,7 +64,7 @@
 
       <b-table-column field="targetActor.updatedAt" :label="$t('Date')" sortable v-slot="props">
         <span :title="$options.filters.formatDateTimeString(props.row.updatedAt)">{{
-          timeago(props.row.updatedAt)
+          formatDistanceToNow(new Date(props.row.updatedAt), { locale: $dateFnsLocale })
         }}</span></b-table-column
       >
 
@@ -100,6 +100,7 @@
 <script lang="ts">
 import { Component, Mixins } from "vue-property-decorator";
 import { SnackbarProgrammatic as Snackbar } from "buefy";
+import { formatDistanceToNow } from "date-fns";
 import { ADD_RELAY, RELAY_FOLLOWINGS, REMOVE_RELAY } from "../../graphql/admin";
 import { IFollower } from "../../types/actor/follower.model";
 import { Paginate } from "../../types/paginate";
@@ -126,6 +127,8 @@ export default class Followings extends Mixins(RelayMixin) {
 
   RelayMixin = RelayMixin;
 
+  formatDistanceToNow = formatDistanceToNow;
+
   async followRelay(e: Event): Promise<void> {
     e.preventDefault();
     try {
diff --git a/js/src/components/Comment/Comment.vue b/js/src/components/Comment/Comment.vue
index d700a2bb8..c5cb07bfa 100644
--- a/js/src/components/Comment/Comment.vue
+++ b/js/src/components/Comment/Comment.vue
@@ -24,7 +24,12 @@
             <strong :class="{ organizer: commentFromOrganizer }">{{ comment.actor.name }}</strong>
             <small>@{{ usernameWithDomain(comment.actor) }}</small>
             <a class="comment-link has-text-grey" :href="commentURL">
-              <small>{{ timeago(new Date(comment.updatedAt)) }}</small>
+              <small>{{
+                formatDistanceToNow(new Date(comment.updatedAt), {
+                  locale: $dateFnsLocale,
+                  addSuffix: true,
+                })
+              }}</small>
             </a>
           </span>
           <a v-else class="comment-link has-text-grey" :href="commentURL">
@@ -130,8 +135,8 @@
 <script lang="ts">
 import { Component, Prop, Vue, Ref } from "vue-property-decorator";
 import EditorComponent from "@/components/Editor.vue";
-import TimeAgo from "javascript-time-ago";
 import { SnackbarProgrammatic as Snackbar } from "buefy";
+import { formatDistanceToNow } from "date-fns";
 import { CommentModel, IComment } from "../../types/comment.model";
 import { CURRENT_ACTOR_CLIENT } from "../../graphql/actor";
 import { IPerson, usernameWithDomain } from "../../types/actor";
@@ -171,18 +176,13 @@ export default class Comment extends Vue {
 
   showReplies = false;
 
-  timeAgoInstance: TimeAgo | null = null;
-
   CommentModeration = CommentModeration;
 
   usernameWithDomain = usernameWithDomain;
 
-  async mounted(): Promise<void> {
-    const localeName = this.$i18n.locale;
-    const locale = await import(`javascript-time-ago/locale/${localeName}`);
-    TimeAgo.addLocale(locale);
-    this.timeAgoInstance = new TimeAgo(localeName);
+  formatDistanceToNow = formatDistanceToNow;
 
+  async mounted(): Promise<void> {
     const { hash } = this.$route;
     if (hash.includes(`#comment-${this.comment.uuid}`)) {
       this.fetchReplies();
@@ -243,13 +243,6 @@ export default class Comment extends Vue {
     this.showReplies = true;
   }
 
-  timeago(dateTime: Date): string {
-    if (this.timeAgoInstance != null) {
-      return this.timeAgoInstance.format(dateTime);
-    }
-    return "";
-  }
-
   get commentSelected(): boolean {
     return this.commentId === this.$route.hash;
   }
@@ -316,8 +309,6 @@ export default class Comment extends Vue {
 }
 </script>
 <style lang="scss" scoped>
-@import "@/variables.scss";
-
 form.reply {
   padding-bottom: 1rem;
 }
diff --git a/js/src/components/Discussion/DiscussionComment.vue b/js/src/components/Discussion/DiscussionComment.vue
index 38d71967c..c6db3188d 100644
--- a/js/src/components/Discussion/DiscussionComment.vue
+++ b/js/src/components/Discussion/DiscussionComment.vue
@@ -43,7 +43,10 @@
         </span>
         <div class="post-infos">
           <span :title="comment.insertedAt | formatDateTimeString">
-            {{ $timeAgo.format(new Date(comment.updatedAt), "twitter") || $t("Right now") }}</span
+            {{
+              formatDistanceToNow(new Date(comment.updatedAt), { locale: $dateFnsLocale }) ||
+              $t("Right now")
+            }}</span
           >
         </div>
       </div>
@@ -53,7 +56,13 @@
           v-if="comment.insertedAt.getTime() !== comment.updatedAt.getTime()"
           :title="comment.updatedAt | formatDateTimeString"
         >
-          {{ $t("Edited {ago}", { ago: $timeAgo.format(new Date(comment.updatedAt)) }) }}
+          {{
+            $t("Edited {ago}", {
+              ago: formatDistanceToNow(new Date(comment.updatedAt), {
+                locale: $dateFnsLocale,
+              }),
+            })
+          }}
         </p>
       </div>
       <div class="comment-deleted" v-else-if="!editMode">
@@ -76,7 +85,8 @@
 </template>
 <script lang="ts">
 import { Component, Prop, Vue } from "vue-property-decorator";
-import { IComment, CommentModel } from "../../types/comment.model";
+import { formatDistanceToNow } from "date-fns";
+import { IComment } from "../../types/comment.model";
 import { usernameWithDomain, IPerson } from "../../types/actor";
 import { CURRENT_ACTOR_CLIENT } from "../../graphql/actor";
 
@@ -99,14 +109,16 @@ export default class DiscussionComment extends Vue {
 
   usernameWithDomain = usernameWithDomain;
 
+  formatDistanceToNow = formatDistanceToNow;
+
   // isReportModalActive: boolean = false;
 
-  toggleEditMode() {
+  toggleEditMode(): void {
     this.updatedComment = this.comment.text;
     this.editMode = !this.editMode;
   }
 
-  updateComment() {
+  updateComment(): void {
     this.comment.text = this.updatedComment;
     this.$emit("update-comment", this.comment);
     this.toggleEditMode();
@@ -114,8 +126,6 @@ export default class DiscussionComment extends Vue {
 }
 </script>
 <style lang="scss" scoped>
-@import "@/variables.scss";
-
 article.comment {
   display: flex;
   border-top: 1px solid #e9e9e9;
diff --git a/js/src/components/Discussion/DiscussionListItem.vue b/js/src/components/Discussion/DiscussionListItem.vue
index ab6bc2938..31f14a1e7 100644
--- a/js/src/components/Discussion/DiscussionListItem.vue
+++ b/js/src/components/Discussion/DiscussionListItem.vue
@@ -16,7 +16,10 @@
       <div class="title-and-date">
         <p class="discussion-minimalist-title">{{ discussion.title }}</p>
         <span :title="actualDate | formatDateTimeString">
-          {{ $timeAgo.format(new Date(actualDate), "twitter") || $t("Right now") }}</span
+          {{
+            formatDistanceToNowStrict(new Date(actualDate), { locale: $dateFnsLocale }) ||
+            $t("Right now")
+          }}</span
         >
       </div>
       <div class="has-text-grey" v-if="!discussion.lastComment.deletedAt">
@@ -28,6 +31,7 @@
 </template>
 <script lang="ts">
 import { Component, Prop, Vue } from "vue-property-decorator";
+import { formatDistanceToNowStrict } from "date-fns";
 import { IDiscussion } from "../../types/discussions";
 import RouteName from "../../router/name";
 
@@ -37,7 +41,9 @@ export default class DiscussionListItem extends Vue {
 
   RouteName = RouteName;
 
-  get htmlTextEllipsis() {
+  formatDistanceToNowStrict = formatDistanceToNowStrict;
+
+  get htmlTextEllipsis(): string {
     const element = document.createElement("div");
     if (this.discussion.lastComment && this.discussion.lastComment.text) {
       element.innerHTML = this.discussion.lastComment.text
@@ -47,7 +53,7 @@ export default class DiscussionListItem extends Vue {
     return element.innerText;
   }
 
-  get actualDate() {
+  get actualDate(): string | Date | undefined {
     if (this.discussion.updatedAt === this.discussion.insertedAt && this.discussion.lastComment) {
       return this.discussion.lastComment.publishedAt;
     }
diff --git a/js/src/components/Editor.vue b/js/src/components/Editor.vue
index ca4f6e82e..c162e4564 100644
--- a/js/src/components/Editor.vue
+++ b/js/src/components/Editor.vue
@@ -552,8 +552,6 @@ export default class EditorComponent extends Vue {
 }
 </script>
 <style lang="scss">
-@import "@/variables.scss";
-
 $color-black: #000;
 $color-white: #eee;
 
diff --git a/js/src/components/Event/DateCalendarIcon.vue b/js/src/components/Event/DateCalendarIcon.vue
index 21d72c23c..c90216f71 100644
--- a/js/src/components/Event/DateCalendarIcon.vue
+++ b/js/src/components/Event/DateCalendarIcon.vue
@@ -27,23 +27,21 @@ export default class DateCalendarIcon extends Vue {
    */
   @Prop({ required: true }) date!: string;
 
-  get dateObj() {
+  get dateObj(): Date {
     return new Date(this.$props.date);
   }
 
-  get month() {
+  get month(): string {
     return this.dateObj.toLocaleString(undefined, { month: "short" });
   }
 
-  get day() {
+  get day(): string {
     return this.dateObj.toLocaleString(undefined, { day: "numeric" });
   }
 }
 </script>
 
 <style lang="scss" scoped>
-@import "../../variables.scss";
-
 time.datetime-container {
   background: $backgrounds;
   border: 1px solid $borders;
diff --git a/js/src/components/Event/EventCard.vue b/js/src/components/Event/EventCard.vue
index 5f0815685..6067ce8a7 100644
--- a/js/src/components/Event/EventCard.vue
+++ b/js/src/components/Event/EventCard.vue
@@ -115,8 +115,6 @@ export default class EventCard extends Vue {
 </script>
 
 <style lang="scss" scoped>
-@import "../../variables";
-
 a.card {
   display: block;
   background: $secondary;
diff --git a/js/src/components/Event/EventListCard.vue b/js/src/components/Event/EventListCard.vue
index 3a02d2759..670f811ba 100644
--- a/js/src/components/Event/EventListCard.vue
+++ b/js/src/components/Event/EventListCard.vue
@@ -275,8 +275,6 @@ export default class EventListCard extends mixins(ActorMixin, EventMixin) {
 </script>
 
 <style lang="scss" scoped>
-@import "../../variables";
-
 article.box {
   div.tag-container {
     position: absolute;
diff --git a/js/src/components/Event/EventListViewCard.vue b/js/src/components/Event/EventListViewCard.vue
index be744e239..c08b4b322 100644
--- a/js/src/components/Event/EventListViewCard.vue
+++ b/js/src/components/Event/EventListViewCard.vue
@@ -104,8 +104,6 @@ export default class EventListViewCard extends mixins(ActorMixin, EventMixin) {
 </script>
 
 <style lang="scss" scoped>
-@import "../../variables";
-
 article.box {
   div.content {
     padding: 5px;
diff --git a/js/src/components/Event/EventMetadataBlock.vue b/js/src/components/Event/EventMetadataBlock.vue
index 79d9af5b3..4aab57660 100644
--- a/js/src/components/Event/EventMetadataBlock.vue
+++ b/js/src/components/Event/EventMetadataBlock.vue
@@ -20,8 +20,6 @@ export default class EventMetadataBlock extends Vue {
 }
 </script>
 <style lang="scss" scoped>
-@import "../../variables.scss";
-
 h2 {
   font-size: 1.8rem;
   font-weight: 500;
diff --git a/js/src/components/Footer.vue b/js/src/components/Footer.vue
index 27105abb3..7be53b1bc 100644
--- a/js/src/components/Footer.vue
+++ b/js/src/components/Footer.vue
@@ -44,8 +44,6 @@ export default class Footer extends Vue {
 }
 </script>
 <style lang="scss" scoped>
-@import "../variables.scss";
-
 footer.footer {
   color: $secondary;
   display: flex;
diff --git a/js/src/components/Group/GroupSection.vue b/js/src/components/Group/GroupSection.vue
index 56d7f9c33..de4c305f4 100644
--- a/js/src/components/Group/GroupSection.vue
+++ b/js/src/components/Group/GroupSection.vue
@@ -32,8 +32,6 @@ export default class GroupSection extends Vue {
 }
 </script>
 <style lang="scss" scoped>
-@import "@/variables.scss";
-
 section {
   display: flex;
   flex-direction: column;
diff --git a/js/src/components/Group/InvitationCard.vue b/js/src/components/Group/InvitationCard.vue
index 13fece9fd..9292bb264 100644
--- a/js/src/components/Group/InvitationCard.vue
+++ b/js/src/components/Group/InvitationCard.vue
@@ -54,7 +54,7 @@
 
 <script lang="ts">
 import { Component, Prop, Vue } from "vue-property-decorator";
-import { IGroup, IMember, usernameWithDomain } from "@/types/actor";
+import { IMember, usernameWithDomain } from "@/types/actor";
 import RouteName from "../../router/name";
 
 @Component
@@ -68,8 +68,6 @@ export default class InvitationCard extends Vue {
 </script>
 
 <style lang="scss" scoped>
-@import "@/variables.scss";
-
 .media:not(.subfield) {
   background: lighten($primary, 40%);
   padding: 10px;
diff --git a/js/src/components/Logo.vue b/js/src/components/Logo.vue
index b2c07567e..be7e20083 100644
--- a/js/src/components/Logo.vue
+++ b/js/src/components/Logo.vue
@@ -4,7 +4,7 @@
 
 <script lang="ts">
 import { Component, Prop, Vue } from "vue-property-decorator";
-// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
 // @ts-ignore
 import MobilizonLogo from "../assets/mobilizon_logo.svg?inline";
 
@@ -18,8 +18,6 @@ export default class Logo extends Vue {
 }
 </script>
 <style lang="scss" scoped>
-@import "../variables.scss";
-
 svg {
   fill: $background-color;
 
diff --git a/js/src/components/NavBar.vue b/js/src/components/NavBar.vue
index db75dd2a6..04e6638be 100644
--- a/js/src/components/NavBar.vue
+++ b/js/src/components/NavBar.vue
@@ -216,8 +216,6 @@ export default class NavBar extends Vue {
 }
 </script>
 <style lang="scss" scoped>
-@import "../variables.scss";
-
 nav {
   .navbar-item {
     a.button {
diff --git a/js/src/components/Post/PostListItem.vue b/js/src/components/Post/PostListItem.vue
index 8a3940518..570a0e898 100644
--- a/js/src/components/Post/PostListItem.vue
+++ b/js/src/components/Post/PostListItem.vue
@@ -5,11 +5,17 @@
   >
     <div class="title-info-wrapper">
       <p class="post-minimalist-title">{{ post.title }}</p>
-      <small class="has-text-grey">{{ $timeAgo.format(new Date(post.insertedAt)) }}</small>
+      <small class="has-text-grey">{{
+        formatDistanceToNow(new Date(post.publishAt || post.insertedAt), {
+          locale: $dateFnsLocale,
+          addSuffix: true,
+        })
+      }}</small>
     </div>
   </router-link>
 </template>
 <script lang="ts">
+import { formatDistanceToNow } from "date-fns";
 import { Component, Prop, Vue } from "vue-property-decorator";
 import RouteName from "../../router/name";
 import { IPost } from "../../types/post.model";
@@ -19,6 +25,8 @@ export default class PostListItem extends Vue {
   @Prop({ required: true, type: Object }) post!: IPost;
 
   RouteName = RouteName;
+
+  formatDistanceToNow = formatDistanceToNow;
 }
 </script>
 <style lang="scss" scoped>
diff --git a/js/src/components/Resource/FolderItem.vue b/js/src/components/Resource/FolderItem.vue
index e4bd9063d..76ef70537 100644
--- a/js/src/components/Resource/FolderItem.vue
+++ b/js/src/components/Resource/FolderItem.vue
@@ -114,8 +114,6 @@ export default class FolderItem extends Mixins(ResourceMixin) {
 }
 </script>
 <style lang="scss" scoped>
-@import "@/variables.scss";
-
 .resource-wrapper {
   display: flex;
   flex: 1;
diff --git a/js/src/components/Resource/ResourceItem.vue b/js/src/components/Resource/ResourceItem.vue
index fcb8e84fa..50f4e7a3e 100644
--- a/js/src/components/Resource/ResourceItem.vue
+++ b/js/src/components/Resource/ResourceItem.vue
@@ -57,8 +57,6 @@ export default class ResourceItem extends Vue {
 }
 </script>
 <style lang="scss" scoped>
-@import "@/variables.scss";
-
 .resource-wrapper {
   display: flex;
   flex: 1;
diff --git a/js/src/components/Resource/ResourceSelector.vue b/js/src/components/Resource/ResourceSelector.vue
index 6cc5ebd8e..755604055 100644
--- a/js/src/components/Resource/ResourceSelector.vue
+++ b/js/src/components/Resource/ResourceSelector.vue
@@ -111,8 +111,6 @@ export default class ResourceSelector extends Vue {
 }
 </script>
 <style lang="scss" scoped>
-@import "../../variables.scss";
-
 .panel {
   a.panel-block {
     cursor: default;
diff --git a/js/src/components/Settings/SettingMenuItem.vue b/js/src/components/Settings/SettingMenuItem.vue
index 73a07a157..2f201ae94 100644
--- a/js/src/components/Settings/SettingMenuItem.vue
+++ b/js/src/components/Settings/SettingMenuItem.vue
@@ -30,8 +30,6 @@ export default class SettingMenuItem extends Vue {
 </script>
 
 <style lang="scss" scoped>
-@import "@/variables.scss";
-
 li.setting-menu-item {
   font-size: 1.05rem;
   background-color: #fff1de;
diff --git a/js/src/components/Settings/SettingMenuSection.vue b/js/src/components/Settings/SettingMenuSection.vue
index 5eb5d8946..cc2d1953a 100644
--- a/js/src/components/Settings/SettingMenuSection.vue
+++ b/js/src/components/Settings/SettingMenuSection.vue
@@ -37,8 +37,6 @@ export default class SettingMenuSection extends Vue {
 </script>
 
 <style lang="scss" scoped>
-@import "@/variables.scss";
-
 li {
   font-size: 1.3rem;
   background-color: $secondary;
diff --git a/js/src/components/Tag.vue b/js/src/components/Tag.vue
index efc45ecc9..64eff4b5d 100644
--- a/js/src/components/Tag.vue
+++ b/js/src/components/Tag.vue
@@ -13,8 +13,6 @@ export default class Tag extends Vue {}
 </script>
 
 <style lang="scss" scoped>
-@import "../variables.scss";
-
 span.tag {
   background: $purple-3;
   color: $violet-2;
diff --git a/js/src/components/Utils/Subtitle.vue b/js/src/components/Utils/Subtitle.vue
index 30d746adf..6f88cc423 100644
--- a/js/src/components/Utils/Subtitle.vue
+++ b/js/src/components/Utils/Subtitle.vue
@@ -12,8 +12,6 @@ import { Component, Vue } from "vue-property-decorator";
 export default class Subtitle extends Vue {}
 </script>
 <style lang="scss" scoped>
-@import "@/variables.scss";
-
 h2 {
   display: block;
   margin: 15px 0 30px;
diff --git a/js/src/components/Utils/VerticalDivider.vue b/js/src/components/Utils/VerticalDivider.vue
index 76516562d..5e1dbe2e9 100644
--- a/js/src/components/Utils/VerticalDivider.vue
+++ b/js/src/components/Utils/VerticalDivider.vue
@@ -8,14 +8,12 @@ import { Component, Prop, Vue } from "vue-property-decorator";
 export default class VerticalDivider extends Vue {
   @Prop({ default: "Or" }) content!: string;
 
-  get dataContent() {
+  get dataContent(): string {
     return this.content.toLocaleUpperCase();
   }
 }
 </script>
 <style lang="scss" scoped>
-@import "@/variables.scss";
-
 .is-divider-vertical[data-content]::after {
   background-color: $body-background-color;
 }
diff --git a/js/src/filters/index.ts b/js/src/filters/index.ts
index ae2108db8..71339a88a 100644
--- a/js/src/filters/index.ts
+++ b/js/src/filters/index.ts
@@ -2,7 +2,7 @@ import nl2br from "@/filters/utils";
 import { formatDateString, formatTimeString, formatDateTimeString } from "./datetime";
 
 export default {
-  install(vue: any) {
+  install(vue: any): void {
     vue.filter("formatDateString", formatDateString);
     vue.filter("formatTimeString", formatTimeString);
     vue.filter("formatDateTimeString", formatDateTimeString);
diff --git a/js/src/i18n/fr_FR.json b/js/src/i18n/fr_FR.json
index d3f370f76..49de14720 100644
--- a/js/src/i18n/fr_FR.json
+++ b/js/src/i18n/fr_FR.json
@@ -204,7 +204,7 @@
   "Duplicate": "Dupliquer",
   "Edit": "Modifier",
   "Edit post": "Éditer le billet",
-  "Edited {ago}": "Édité {ago}",
+  "Edited {ago}": "Édité il y a {ago}",
   "Eg: Stockholm, Dance, Chess…": "Par exemple : Lyon, Danse, Bridge…",
   "Either on the {instance} instance or on another instance.": "Sur l'instance {instance} ou bien sur une autre instance.",
   "Either the account is already validated, either the validation token is incorrect.": "Soit le compte est déjà validé, soit le jeton de validation est incorrect.",
diff --git a/js/src/i18n/index.ts b/js/src/i18n/index.ts
deleted file mode 100644
index af920d2e7..000000000
--- a/js/src/i18n/index.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-/* eslint-disable @typescript-eslint/camelcase */
-
-import ar from "./ar.json";
-import be from "./be.json";
-import ca from "./ca.json";
-import cs from "./cs.json";
-import de from "./de.json";
-import en_US from "./en_US.json";
-import es from "./es.json";
-import fi from "./fi.json";
-import fr_FR from "./fr_FR.json";
-import it from "./it.json";
-import ja from "./ja.json";
-import nl from "./nl.json";
-import oc from "./oc.json";
-import pl from "./pl.json";
-import pt from "./pt.json";
-import pt_BR from "./pt_BR.json";
-import ru from "./ru.json";
-import sv from "./sv.json";
-
-export default {
-  ar,
-  be,
-  ca,
-  cs,
-  de,
-  en: en_US,
-  en_US,
-  es,
-  fi,
-  fr: fr_FR,
-  fr_FR,
-  it,
-  ja,
-  nl,
-  oc,
-  pl,
-  pt,
-  pt_BR,
-  ru,
-  sv,
-};
diff --git a/js/src/i18n/langs.json b/js/src/i18n/langs.json
index d9916fb71..a7c51b6fa 100644
--- a/js/src/i18n/langs.json
+++ b/js/src/i18n/langs.json
@@ -1,19 +1,14 @@
 {
   "ar": "العربية",
-  "bg": "Български",
   "be": "Беларуская мова",
-  "br": "Brezhoneg",
   "ca": "Català",
-  "co": "Corsu",
   "cs": "čeština",
   "de": "Deutsch",
   "en": "English",
-  "eo": "Esperanto",
   "es": "Español",
   "fi": "suomi",
   "fr": "Français",
   "gl": "Galego",
-  "hu": "Magyar",
   "it": "Italiano",
   "ja": "日本語",
   "nl": "Dutch",
@@ -22,9 +17,5 @@
   "pt": "Português",
   "pt_PT": "Português (Portugal)",
   "ru": "Русский",
-  "sq": "Shqip",
-  "sv": "Svenska",
-  "tr": "Türkçe",
-  "vi": "Tiếng Việt",
-  "zh_Hant_TW": "繁體中文(台灣)"
+  "sv": "Svenska"
 }
diff --git a/js/src/main.ts b/js/src/main.ts
index b425b56ee..5fcc4addb 100644
--- a/js/src/main.ts
+++ b/js/src/main.ts
@@ -6,33 +6,17 @@ import Component from "vue-class-component";
 import VueScrollTo from "vue-scrollto";
 import VueMeta from "vue-meta";
 import VTooltip from "v-tooltip";
-import TimeAgo from "javascript-time-ago";
 import App from "./App.vue";
 import router from "./router";
 import { NotifierPlugin } from "./plugins/notifier";
-import { DateFnsPlugin } from "./plugins/dateFns";
 import filters from "./filters";
 import { i18n } from "./utils/i18n";
-import messages from "./i18n";
 import apolloProvider from "./vue-apollo";
 
 Vue.config.productionTip = false;
 
-let language = document.documentElement.getAttribute("lang") as string;
-language =
-  language ||
-  ((window.navigator as any).userLanguage || window.navigator.language).replace(/-/, "_");
-export const locale =
-  language && messages.hasOwnProperty(language) ? language : language.split("-")[0];
-
-import(`javascript-time-ago/locale/${locale}`).then((localeFile) => {
-  TimeAgo.addLocale(localeFile);
-  Vue.prototype.$timeAgo = new TimeAgo(locale);
-});
-
 Vue.use(Buefy);
 Vue.use(NotifierPlugin);
-Vue.use(DateFnsPlugin, { locale });
 Vue.use(filters);
 Vue.use(VueMeta);
 Vue.use(VueScrollTo);
diff --git a/js/src/mixins/relay.ts b/js/src/mixins/relay.ts
index a01fbdb46..295bccec9 100644
--- a/js/src/mixins/relay.ts
+++ b/js/src/mixins/relay.ts
@@ -1,7 +1,6 @@
 import { Component, Vue, Ref } from "vue-property-decorator";
 import { ActorType, IActor } from "@/types/actor";
 import { IFollower } from "@/types/actor/follower.model";
-import TimeAgo from "javascript-time-ago";
 
 @Component
 export default class RelayMixin extends Vue {
@@ -13,20 +12,11 @@ export default class RelayMixin extends Vue {
 
   perPage = 10;
 
-  timeAgoInstance: TimeAgo | null = null;
-
-  async mounted() {
-    const localeName = this.$i18n.locale;
-    const locale = await import(`javascript-time-ago/locale/${localeName}`);
-    TimeAgo.addLocale(locale);
-    this.timeAgoInstance = new TimeAgo(localeName);
-  }
-
-  toggle(row: object) {
+  toggle(row: Record<string, unknown>): void {
     this.table.toggleDetails(row);
   }
 
-  async onPageChange(page: number) {
+  async onPageChange(page: number): Promise<void> {
     this.page = page;
     await this.$apollo.queries.relayFollowings.fetchMore({
       variables: {
@@ -53,11 +43,4 @@ export default class RelayMixin extends Vue {
       (actor.preferredUsername === "relay" || actor.preferredUsername === actor.domain)
     );
   }
-
-  timeago(dateTime: string): string {
-    if (this.timeAgoInstance != null) {
-      return this.timeAgoInstance.format(new Date(dateTime));
-    }
-    return "";
-  }
 }
diff --git a/js/src/plugins/dateFns.ts b/js/src/plugins/dateFns.ts
index c989092f6..cf2114721 100644
--- a/js/src/plugins/dateFns.ts
+++ b/js/src/plugins/dateFns.ts
@@ -1,5 +1,5 @@
-import Vue from "vue";
 import Locale from "date-fns";
+import VueInstance from "vue";
 
 declare module "vue/types/vue" {
   interface Vue {
@@ -7,8 +7,8 @@ declare module "vue/types/vue" {
   }
 }
 
-export function DateFnsPlugin(vue: typeof Vue, { locale }: { locale: string }): void {
+export function DateFnsPlugin(vue: typeof VueInstance, { locale }: { locale: string }): void {
   import(`date-fns/locale/${locale}/index.js`).then((localeEntity) => {
-    Vue.prototype.$dateFnsLocale = localeEntity;
+    VueInstance.prototype.$dateFnsLocale = localeEntity;
   });
 }
diff --git a/js/src/plugins/notifier.ts b/js/src/plugins/notifier.ts
index 8341faa66..5000bf986 100644
--- a/js/src/plugins/notifier.ts
+++ b/js/src/plugins/notifier.ts
@@ -1,4 +1,5 @@
-import Vue from "vue";
+/* eslint-disable no-shadow */
+import VueInstance from "vue";
 import { ColorModifiers } from "buefy/types/helpers.d";
 import { Route, RawLocation } from "vue-router";
 
@@ -12,39 +13,39 @@ declare module "vue/types/vue" {
     beforeRouteEnter?(
       to: Route,
       from: Route,
-      next: (to?: RawLocation | false | ((vm: Vue) => void)) => void
+      next: (to?: RawLocation | false | ((vm: VueInstance) => void)) => void
     ): void;
 
     beforeRouteLeave?(
       to: Route,
       from: Route,
-      next: (to?: RawLocation | false | ((vm: Vue) => void)) => void
+      next: (to?: RawLocation | false | ((vm: VueInstance) => void)) => void
     ): void;
 
     beforeRouteUpdate?(
       to: Route,
       from: Route,
-      next: (to?: RawLocation | false | ((vm: Vue) => void)) => void
+      next: (to?: RawLocation | false | ((vm: VueInstance) => void)) => void
     ): void;
   }
 }
 
 export class Notifier {
-  private readonly vue: typeof Vue;
+  private readonly vue: typeof VueInstance;
 
-  constructor(vue: typeof Vue) {
+  constructor(vue: typeof VueInstance) {
     this.vue = vue;
   }
 
-  success(message: string) {
+  success(message: string): void {
     this.notification(message, "is-success");
   }
 
-  error(message: string) {
+  error(message: string): void {
     this.notification(message, "is-danger");
   }
 
-  info(message: string) {
+  info(message: string): void {
     this.notification(message, "is-info");
   }
 
@@ -60,6 +61,6 @@ export class Notifier {
 }
 
 /* eslint-disable */
-export function NotifierPlugin(vue: typeof Vue): void {
+export function NotifierPlugin(vue: typeof VueInstance): void {
   vue.prototype.$notifier = new Notifier(vue);
 }
diff --git a/js/src/utils/i18n.ts b/js/src/utils/i18n.ts
index a6c867c10..7c412204d 100644
--- a/js/src/utils/i18n.ts
+++ b/js/src/utils/i18n.ts
@@ -1,18 +1,70 @@
 import Vue from "vue";
 import VueI18n from "vue-i18n";
-import messages from "../i18n/index";
+import { DateFnsPlugin } from "@/plugins/dateFns";
+import en from "../i18n/en_US.json";
+import langs from "../i18n/langs.json";
+
+const DEFAULT_LOCALE = "en";
 
 let language = document.documentElement.getAttribute("lang") as string;
 language = language || ((window.navigator as any).userLanguage || window.navigator.language).replace(/-/, "_");
-export const locale = language && messages.hasOwnProperty(language) ? language : language.split("-")[0];
+export const locale =
+  language && Object.prototype.hasOwnProperty.call(langs, language) ? language : language.split("-")[0];
 
 Vue.use(VueI18n);
 
+console.log(en);
+console.log(locale);
 export const i18n = new VueI18n({
-  locale, // set locale
-  messages, // set locale messages
-  fallbackLocale: "en_US",
+  locale: DEFAULT_LOCALE, // set locale
+  messages: (en as unknown) as VueI18n.LocaleMessages, // set locale messages
+  fallbackLocale: "en",
 });
+console.log(i18n);
+
+Vue.use(DateFnsPlugin, { locale });
+
+const loadedLanguages = ["en"];
+
+function setI18nLanguage(lang: string): string {
+  i18n.locale = lang;
+  return lang;
+}
+
+function fileForLanguage(lang: string) {
+  const matches: Record<string, string> = {
+    fr: "fr_FR",
+    en: "en_US",
+  };
+  if (Object.prototype.hasOwnProperty.call(matches, lang)) {
+    return matches[lang];
+  }
+  return lang;
+}
+
+export async function loadLanguageAsync(lang: string): Promise<string> {
+  // If the same language
+  if (i18n.locale === lang) {
+    return Promise.resolve(setI18nLanguage(lang));
+  }
+
+  // If the language was already loaded
+  if (loadedLanguages.includes(lang)) {
+    return Promise.resolve(setI18nLanguage(lang));
+  }
+
+  console.log(fileForLanguage(lang));
+  // If the language hasn't been loaded yet
+  return import(/* webpackChunkName: "lang-[request]" */ `@/i18n/${fileForLanguage(lang)}.json`).then(
+    (newMessages: any) => {
+      i18n.setLocaleMessage(lang, newMessages.default);
+      loadedLanguages.push(lang);
+      return setI18nLanguage(lang);
+    }
+  );
+}
+
+loadLanguageAsync(locale);
 
 export function formatList(list: string[]): string {
   if (window.Intl && Intl.ListFormat) {
diff --git a/js/src/variables.scss b/js/src/variables.scss
index c005c974a..9323842af 100644
--- a/js/src/variables.scss
+++ b/js/src/variables.scss
@@ -135,7 +135,3 @@ $subtitle-sup-size: 15px;
 $breadcrumb-item-color: $primary;
 $checkbox-background-color: #fff;
 $title-color: $violet-3;
-
-@import "~bulma";
-@import "~bulma-divider";
-@import "~buefy/src/scss/buefy";
diff --git a/js/src/views/About.vue b/js/src/views/About.vue
index dc16f4e23..1061e72b6 100644
--- a/js/src/views/About.vue
+++ b/js/src/views/About.vue
@@ -119,8 +119,6 @@ export default class About extends Vue {
 </script>
 
 <style lang="scss" scoped>
-@import "../variables.scss";
-
 .hero.is-primary {
   background: $background-color;
 
diff --git a/js/src/views/About/AboutInstance.vue b/js/src/views/About/AboutInstance.vue
index 175bca407..13416dec4 100644
--- a/js/src/views/About/AboutInstance.vue
+++ b/js/src/views/About/AboutInstance.vue
@@ -122,8 +122,6 @@ export default class AboutInstance extends Vue {
 </script>
 
 <style lang="scss" scoped>
-@import "../../variables.scss";
-
 section {
   &:not(:first-child) {
     margin: 2rem auto;
diff --git a/js/src/views/About/AboutMobilizon.vue b/js/src/views/About/AboutMobilizon.vue
index 0e36abe5c..0f26324b9 100644
--- a/js/src/views/About/AboutMobilizon.vue
+++ b/js/src/views/About/AboutMobilizon.vue
@@ -166,8 +166,6 @@ export default class AboutMobilizon extends Vue {}
 </script>
 
 <style lang="scss" scoped>
-@import "../../variables.scss";
-
 .hero.is-primary {
   background: $background-color;
   .subtitle {
diff --git a/js/src/views/About/Privacy.vue b/js/src/views/About/Privacy.vue
index b6ee7e69f..8f99c52af 100644
--- a/js/src/views/About/Privacy.vue
+++ b/js/src/views/About/Privacy.vue
@@ -51,8 +51,6 @@ export default class Privacy extends Vue {
 }
 </script>
 <style lang="scss" scoped>
-@import "@/variables.scss";
-
 main > .container {
   background: $white;
 
diff --git a/js/src/views/About/Rules.vue b/js/src/views/About/Rules.vue
index 3750b5acc..c154ba2c4 100644
--- a/js/src/views/About/Rules.vue
+++ b/js/src/views/About/Rules.vue
@@ -27,8 +27,6 @@ export default class Rules extends Vue {
 }
 </script>
 <style lang="scss" scoped>
-@import "@/variables.scss";
-
 main > .container {
   background: $white;
 }
diff --git a/js/src/views/Account/Profile.vue b/js/src/views/Account/Profile.vue
index cfc4bb5b9..9cd1b8e00 100644
--- a/js/src/views/Account/Profile.vue
+++ b/js/src/views/Account/Profile.vue
@@ -144,8 +144,3 @@ export default class Profile extends Vue {
   }
 }
 </script>
-<style lang="scss">
-@import "../../variables";
-@import "~bulma/sass/utilities/_all";
-@import "~bulma/sass/components/dropdown.sass";
-</style>
diff --git a/js/src/views/Account/Register.vue b/js/src/views/Account/Register.vue
index 1b7a62536..99fb4b2fc 100644
--- a/js/src/views/Account/Register.vue
+++ b/js/src/views/Account/Register.vue
@@ -168,7 +168,6 @@ export default class Register extends mixins(identityEditionMixin) {
 </script>
 
 <style lang="scss" scoped>
-@import "../../variables.scss";
 .avatar-enter-active {
   transition: opacity 1s ease;
 }
diff --git a/js/src/views/Admin/Settings.vue b/js/src/views/Admin/Settings.vue
index 214faa8ca..61d108e7a 100644
--- a/js/src/views/Admin/Settings.vue
+++ b/js/src/views/Admin/Settings.vue
@@ -359,8 +359,6 @@ export default class Settings extends Vue {
 }
 </script>
 <style lang="scss" scoped>
-@import "../../variables.scss";
-
 .notification a {
   color: $primary !important;
   text-decoration: underline !important;
diff --git a/js/src/views/Admin/Users.vue b/js/src/views/Admin/Users.vue
index 218e1f28a..b554328bb 100644
--- a/js/src/views/Admin/Users.vue
+++ b/js/src/views/Admin/Users.vue
@@ -145,14 +145,13 @@ export default class Users extends Vue {
     });
   }
 
-  onFiltersChange({ email }: { email: string }) {
+  onFiltersChange({ email }: { email: string }): void {
     this.email = email;
   }
 }
 </script>
 
 <style lang="scss" scoped>
-@import "../../variables.scss";
 a.profile,
 a.user-profile {
   text-decoration: none;
diff --git a/js/src/views/Event/Edit.vue b/js/src/views/Event/Edit.vue
index f58bed173..2fb16432b 100644
--- a/js/src/views/Event/Edit.vue
+++ b/js/src/views/Event/Edit.vue
@@ -296,8 +296,6 @@
 </template>
 
 <style lang="scss" scoped>
-@import "@/variables.scss";
-
 main section > .container {
   background: $white;
 }
diff --git a/js/src/views/Event/Event.vue b/js/src/views/Event/Event.vue
index a19ff0983..d02327424 100644
--- a/js/src/views/Event/Event.vue
+++ b/js/src/views/Event/Event.vue
@@ -1044,8 +1044,6 @@ export default class Event extends EventMixin {
 }
 </script>
 <style lang="scss" scoped>
-@import "../../variables";
-
 .section {
   padding: 1rem 2rem 4rem;
 }
diff --git a/js/src/views/Event/MyEvents.vue b/js/src/views/Event/MyEvents.vue
index de6bc03e2..fe172894b 100644
--- a/js/src/views/Event/MyEvents.vue
+++ b/js/src/views/Event/MyEvents.vue
@@ -278,8 +278,6 @@ export default class MyEvents extends Vue {
 
 <!-- Add "scoped" attribute to limit CSS to this component only -->
 <style lang="scss" scoped>
-@import "../../variables";
-
 main > .container {
   background: $white;
 }
diff --git a/js/src/views/Event/Participants.vue b/js/src/views/Event/Participants.vue
index 116f16536..139b37620 100644
--- a/js/src/views/Event/Participants.vue
+++ b/js/src/views/Event/Participants.vue
@@ -365,7 +365,7 @@ export default class Participants extends Vue {
 
   nl2br = nl2br;
 
-  toggleQueueDetails(row: IParticipant) {
+  toggleQueueDetails(row: IParticipant): void {
     if (row.metadata.message && row.metadata.message.length < MESSAGE_ELLIPSIS_LENGTH) return;
     this.queueTable.toggleDetails(row);
   }
@@ -374,8 +374,6 @@ export default class Participants extends Vue {
 
 <!-- Add "scoped" attribute to limit CSS to this component only -->
 <style lang="scss" scoped>
-@import "../../variables.scss";
-
 section {
   padding: 1rem 0;
 }
diff --git a/js/src/views/Group/Group.vue b/js/src/views/Group/Group.vue
index d0b811d29..cbc85e31e 100644
--- a/js/src/views/Group/Group.vue
+++ b/js/src/views/Group/Group.vue
@@ -546,8 +546,6 @@ export default class Group extends mixins(GroupMixin) {
 }
 </script>
 <style lang="scss" scoped>
-@import "../../variables.scss";
-
 div.container {
   background: white;
   margin-bottom: 3rem;
diff --git a/js/src/views/Group/MyGroups.vue b/js/src/views/Group/MyGroups.vue
index a09232a32..ae9ee9494 100644
--- a/js/src/views/Group/MyGroups.vue
+++ b/js/src/views/Group/MyGroups.vue
@@ -120,8 +120,6 @@ export default class MyEvents extends Vue {
 
 <!-- Add "scoped" attribute to limit CSS to this component only -->
 <style lang="scss" scoped>
-@import "../../variables";
-
 main > .container {
   background: $white;
 }
diff --git a/js/src/views/Home.vue b/js/src/views/Home.vue
index 6933aff6e..3478c1001 100644
--- a/js/src/views/Home.vue
+++ b/js/src/views/Home.vue
@@ -338,8 +338,6 @@ export default class Home extends Vue {
 </script>
 
 <style lang="scss" scoped>
-@import "@/variables.scss";
-
 main > div > .container {
   background: $white;
 }
diff --git a/js/src/views/Interact.vue b/js/src/views/Interact.vue
index 898ab60ac..1c018c6f4 100644
--- a/js/src/views/Interact.vue
+++ b/js/src/views/Interact.vue
@@ -58,8 +58,6 @@ export default class Interact extends Vue {
 }
 </script>
 <style lang="scss">
-@import "@/variables.scss";
-
 main > .container {
   background: $white;
 }
diff --git a/js/src/views/Moderation/Report.vue b/js/src/views/Moderation/Report.vue
index 990d137db..94c3b4f4e 100644
--- a/js/src/views/Moderation/Report.vue
+++ b/js/src/views/Moderation/Report.vue
@@ -443,8 +443,6 @@ export default class Report extends Vue {
 }
 </script>
 <style lang="scss" scoped>
-@import "@/variables.scss";
-
 tbody td img.image,
 .note img.image {
   display: inline;
diff --git a/js/src/views/Posts/Post.vue b/js/src/views/Posts/Post.vue
index 50663a539..9e5228af6 100644
--- a/js/src/views/Posts/Post.vue
+++ b/js/src/views/Posts/Post.vue
@@ -129,8 +129,6 @@ export default class Post extends Vue {
 }
 </script>
 <style lang="scss" scoped>
-@import "../../variables.scss";
-
 article {
   section.heading-section {
     text-align: center;
diff --git a/js/src/views/Search.vue b/js/src/views/Search.vue
index d19a55011..b101bcc4e 100644
--- a/js/src/views/Search.vue
+++ b/js/src/views/Search.vue
@@ -366,8 +366,6 @@ export default class Search extends Vue {
 </script>
 
 <style scoped lang="scss">
-@import "@/variables.scss";
-
 main > .container {
   background: $white;
 
diff --git a/js/src/views/Settings/AccountSettings.vue b/js/src/views/Settings/AccountSettings.vue
index 6b84d531f..8e92b7167 100644
--- a/js/src/views/Settings/AccountSettings.vue
+++ b/js/src/views/Settings/AccountSettings.vue
@@ -323,8 +323,6 @@ export default class AccountSettings extends Vue {
 }
 </script>
 <style lang="scss">
-@import "@/variables.scss";
-
 .setting-title {
   margin-top: 2rem;
   margin-bottom: 1rem;
diff --git a/js/src/views/Settings/Notifications.vue b/js/src/views/Settings/Notifications.vue
index 9518b25e1..f020bf16d 100644
--- a/js/src/views/Settings/Notifications.vue
+++ b/js/src/views/Settings/Notifications.vue
@@ -155,8 +155,6 @@ export default class Notifications extends Vue {
 </script>
 
 <style lang="scss" scoped>
-@import "../../variables.scss";
-
 .field {
   &:not(:last-child) {
     margin-bottom: 1.5rem;
diff --git a/js/src/views/Settings/Preferences.vue b/js/src/views/Settings/Preferences.vue
index 12238d850..7e82a5c3b 100644
--- a/js/src/views/Settings/Preferences.vue
+++ b/js/src/views/Settings/Preferences.vue
@@ -17,7 +17,7 @@
           v-model="$i18n.locale"
           :placeholder="$t('Select a language')"
         >
-          <option v-for="(language, lang) in languages" :value="lang" :key="lang">
+          <option v-for="(language, lang) in langs" :value="lang" :key="lang">
             {{ language }}
           </option>
         </b-select>
@@ -73,8 +73,10 @@ export default class Preferences extends Vue {
 
   RouteName = RouteName;
 
+  langs: Record<string, string> = langs;
+
   @Watch("loggedUser")
-  setSavedTimezone(loggedUser: IUser) {
+  setSavedTimezone(loggedUser: IUser): void {
     if (loggedUser && loggedUser.settings.timezone) {
       this.selectedTimezone = loggedUser.settings.timezone;
     } else {
@@ -87,22 +89,24 @@ export default class Preferences extends Vue {
     }
   }
 
+  // eslint-disable-next-line class-methods-use-this
   sanitize(timezone: string): string {
     return timezone.split("_").join(" ").replace("St ", "St. ").split("/").join(" - ");
   }
 
-  get timezones() {
+  get timezones(): Record<string, string[]> {
     if (!this.config || !this.config.timezones) return {};
     return this.config.timezones.reduce((acc: { [key: string]: Array<string> }, val: string) => {
       const components = val.split("/");
       const [prefix, suffix] = [components.shift() as string, components.join("/")];
       const pushOrCreate = (
-        acc: { [key: string]: Array<string> },
-        prefix: string,
-        suffix: string
+        acc2: { [key: string]: Array<string> },
+        prefix2: string,
+        suffix2: string
       ) => {
-        (acc[prefix] = acc[prefix] || []).push(suffix);
-        return acc;
+        // eslint-disable-next-line no-param-reassign
+        (acc2[prefix2] = acc2[prefix2] || []).push(suffix2);
+        return acc2;
       };
       if (suffix) {
         return pushOrCreate(acc, prefix, suffix);
@@ -111,22 +115,8 @@ export default class Preferences extends Vue {
     }, {});
   }
 
-  get languages(): object {
-    return this.$i18n.availableLocales.reduce((acc: object, lang: string) => {
-      // @ts-ignore
-      if (langs[lang]) {
-        return {
-          ...acc,
-          // @ts-ignore
-          [lang]: langs[lang],
-        };
-      }
-      return acc;
-    }, {} as object);
-  }
-
   @Watch("selectedTimezone")
-  async updateTimezone() {
+  async updateTimezone(): Promise<void> {
     if (this.selectedTimezone !== this.loggedUser.settings.timezone) {
       await this.$apollo.mutate<{ setUserSetting: string }>({
         mutation: SET_USER_SETTINGS,
@@ -138,7 +128,7 @@ export default class Preferences extends Vue {
   }
 
   @Watch("$i18n.locale")
-  async updateLocale() {
+  async updateLocale(): Promise<void> {
     await this.$apollo.mutate({
       mutation: UPDATE_USER_LOCALE,
       variables: {
diff --git a/js/src/views/User/Register.vue b/js/src/views/User/Register.vue
index 43c7a37fd..f57f6dcf4 100644
--- a/js/src/views/User/Register.vue
+++ b/js/src/views/User/Register.vue
@@ -212,9 +212,6 @@ export default class Register extends Vue {
 </script>
 
 <style lang="scss" scoped>
-@import "../../variables";
-@import "../../common.scss";
-
 .avatar-enter-active {
   transition: opacity 1s ease;
 }
diff --git a/js/tsconfig.json b/js/tsconfig.json
index d1299c7f8..692d8f578 100644
--- a/js/tsconfig.json
+++ b/js/tsconfig.json
@@ -7,6 +7,7 @@
     "importHelpers": true,
     "moduleResolution": "node",
     "experimentalDecorators": true,
+    "skipLibCheck": true,
     "esModuleInterop": true,
     "allowSyntheticDefaultImports": true,
     "resolveJsonModule": true,
diff --git a/js/vue.config.js b/js/vue.config.js
index 7ed74eb13..e10400d19 100644
--- a/js/vue.config.js
+++ b/js/vue.config.js
@@ -5,6 +5,7 @@ module.exports = {
   runtimeCompiler: true,
   lintOnSave: true,
   filenameHashing: true,
+  productionSourceMap: false,
   outputDir: path.resolve(__dirname, "../priv/static"),
   configureWebpack: (config) => {
     // Limit the used memory when building
@@ -25,6 +26,17 @@ module.exports = {
 
     config.plugins.push(new ForkTsCheckerWebpackPlugin(forkTsCheckerOptions));
   },
+  chainWebpack: (config) => {
+    // remove the prefetch plugin
+    config.plugins.delete("prefetch");
+  },
+  css: {
+    loaderOptions: {
+      scss: {
+        additionalData: `@import "@/variables.scss";`,
+      },
+    },
+  },
   // configureWebpack: {
   //   optimization: {
   //     splitChunks: {
diff --git a/js/yarn.lock b/js/yarn.lock
index 2dc473f07..e6d5cfacf 100644
--- a/js/yarn.lock
+++ b/js/yarn.lock
@@ -1352,11 +1352,6 @@
   dependencies:
     "@types/node" "*"
 
-"@types/javascript-time-ago@^2.0.1":
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/@types/javascript-time-ago/-/javascript-time-ago-2.0.1.tgz#819ec39b467409e2fd6acb42bc53ae7d631bbdb0"
-  integrity sha512-6QWXsuqzfUMfsg1DTJan/MfUi80LGS1TOohSqxlgpBZEHH344xpl3LzgANTp7PPWf7Z/9S0l14RMQPF0vH7MIg==
-
 "@types/json-schema@^7.0.3", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6":
   version "7.0.6"
   resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0"
@@ -7988,13 +7983,6 @@ javascript-stringify@^2.0.0, javascript-stringify@^2.0.1:
   resolved "https://registry.yarnpkg.com/javascript-stringify/-/javascript-stringify-2.0.1.tgz#6ef358035310e35d667c675ed63d3eb7c1aa19e5"
   integrity sha512-yV+gqbd5vaOYjqlbk16EG89xB5udgjqQF3C5FAORDg4f/IS1Yc5ERCv5e/57yBcfJYw05V5JyIXabhwb75Xxow==
 
-javascript-time-ago@^2.0.4:
-  version "2.2.8"
-  resolved "https://registry.yarnpkg.com/javascript-time-ago/-/javascript-time-ago-2.2.8.tgz#d2821816a648f4659f605e030418af7949f0564e"
-  integrity sha512-VU2GZ88QYl7zEfnKe2VecnPlXunr1awIAf21S13CRUUYlk6cVbmA81GApMXHIbDUfYfsJVcPjjB76KLEPO4fGA==
-  dependencies:
-    relative-time-format "^1.0.5"
-
 jest-worker@^25.4.0:
   version "25.5.0"
   resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-25.5.0.tgz#2611d071b79cea0f43ee57a3d118593ac1547db1"
@@ -11941,11 +11929,6 @@ relateurl@0.2.x:
   resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9"
   integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=
 
-relative-time-format@^1.0.5:
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/relative-time-format/-/relative-time-format-1.0.5.tgz#3fb7c76ae39156afe0a3a7ff0cb7bf30aa0f0fb6"
-  integrity sha512-MAgx/YKcUQYJpIaWcfetPstElnWf26JxVis4PirdwVrrymFdbxyCSm6yENpfB1YuwFbtHSHksN3aBajVNxk10Q==
-
 remark-parse@^7.0.0:
   version "7.0.2"
   resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-7.0.2.tgz#41e7170d9c1d96c3d32cf1109600a9ed50dba7cf"