diff --git a/src/App.vue b/src/App.vue
index bf1fde0a457c4a5bf45222170399994b91f8319f..42155f8be07861bfb391fda0ac56f8c2456bc973 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -5,51 +5,7 @@
       <p class="about-link"><a href="#">OM ORDBØKENE</a></p>
       <p class="sub-title"><a href="/">Bokmålsordboka | Nynorskordboka – rett norsk</a></p>
     </header>
-    <main :class="(article.error || article.lemmas.length || search_results.length || waiting) ? '' : 'welcome  '">
-      <div class="search_container">
-        <div class="lang_select_container">
-          <v-radio-group row v-model="lang">
-            <template v-slot:label>
-              <span>VIS TREFF I</span>
-            </template>
-            <v-radio value="bob,nob" color="secondary">
-              <template v-slot:label>
-                <span>begge<span class="verbose"> ordbøkene</span></span>
-              </template>
-            </v-radio>
-            <v-radio value="bob" color="secondary">
-              <template v-slot:label>
-                <span>bokmål</span>
-              </template>
-            </v-radio>
-            <v-radio value="nob" color="secondary">
-              <template v-slot:label>
-                <span>nynorsk</span>
-              </template>
-            </v-radio>
-          </v-radio-group>
-        </div>
-        <Autocomplete @submit="select_result" :endpoint="api_pref">
-        </Autocomplete>
-      </div>
-      <div id="spinner">
-        <v-progress-circular indeterminate color="secondary" size="120" v-show="waiting"></v-progress-circular>
-      </div>
-      <SearchResults :hits="search_results" :lang="lang" @article-click="article_link_click" v-show="! waiting" />
-      <div id="single_article_container">
-        <Article :key="article_key" :article="article" @article-click="article_link_click" />
-      </div>
-      <div class="welcome" v-show="! (article.error || article.lemmas.length || search_results.length || waiting)">
-        <div class="monthly">
-          <div>
-            <Article :article="monthly_bm" @article-click="article_link_click" />
-          </div>
-          <div>
-            <Article :article="monthly_nn" @article-click="article_link_click" />
-          </div>
-        </div>
-      </div>
-    </main>
+    <router-view></router-view>
     <footer>
       <div>
         <img id="srlogo" src="./assets/Sprakradet_logo_neg.png" alt="">
@@ -173,7 +129,7 @@ function navigate_to_word(self, word) {
 }
 
 export default {
-  name: 'app',
+  name: 'App',
   data: function() {
     return {
       article_key: 0,
diff --git a/src/Root.vue b/src/Root.vue
new file mode 100644
index 0000000000000000000000000000000000000000..083049bd4e4f703469821ab82edfba3c2bf5c064
--- /dev/null
+++ b/src/Root.vue
@@ -0,0 +1,3 @@
+<template>
+  <router-view/>
+</template>
diff --git a/src/components/About.vue b/src/components/About.vue
new file mode 100644
index 0000000000000000000000000000000000000000..03abe6024775aff6a3ed9d0f0585a20dc850779e
--- /dev/null
+++ b/src/components/About.vue
@@ -0,0 +1,3 @@
+<template id="">
+  <h1>Blæh</h1>
+</template>
diff --git a/src/components/DictionaryView.vue b/src/components/DictionaryView.vue
new file mode 100644
index 0000000000000000000000000000000000000000..363e315532c2612dfa68c92fff7e5b83a8c40b4b
--- /dev/null
+++ b/src/components/DictionaryView.vue
@@ -0,0 +1,442 @@
+<template>
+  <main :class="(article.error || article.lemmas.length || search_results.length || waiting) ? '' : 'welcome  '">
+    <div class="search_container">
+      <div class="lang_select_container">
+        <v-radio-group row v-model="lang">
+          <template v-slot:label>
+            <span>VIS TREFF I</span>
+          </template>
+          <v-radio value="bob,nob" color="secondary">
+            <template v-slot:label>
+              <span>begge<span class="verbose"> ordbøkene</span></span>
+            </template>
+          </v-radio>
+          <v-radio value="bob" color="secondary">
+            <template v-slot:label>
+              <span>bokmål</span>
+            </template>
+          </v-radio>
+          <v-radio value="nob" color="secondary">
+            <template v-slot:label>
+              <span>nynorsk</span>
+            </template>
+          </v-radio>
+        </v-radio-group>
+      </div>
+      <Autocomplete @submit="select_result" :endpoint="api_pref">
+      </Autocomplete>
+    </div>
+    <div id="spinner">
+      <v-progress-circular indeterminate color="secondary" size="120" v-show="waiting"></v-progress-circular>
+    </div>
+    <SearchResults :hits="search_results" :lang="lang" @article-click="article_link_click" v-show="! waiting" />
+    <div id="single_article_container">
+      <Article :key="article_key" :article="article" @article-click="article_link_click" />
+    </div>
+    <div class="welcome" v-show="! (article.error || article.lemmas.length || search_results.length || waiting)">
+      <div class="monthly">
+        <div>
+          <Article :article="monthly_bm" @article-click="article_link_click" />
+        </div>
+        <div>
+          <Article :article="monthly_nn" @article-click="article_link_click" />
+        </div>
+      </div>
+    </div>
+  </main>
+</template>
+
+<script>
+import axios from "axios"
+import entities from '../utils/entities.js'
+import Article from './Article.vue'
+import SearchResults from './SearchResults.vue'
+import Autocomplete from './Autocomplete.vue'
+
+var api_endpoint = 'https://beta.ordbok.uib.no/api/dict'
+
+axios.interceptors.request.use(function (config) {
+    config.headers["x-api-key"] = "ZkYiyRVXxH86ijsvhx3cH4SY5Iik2ijI3BKVJGMm"
+    return config;
+  }, function (error) {
+    return Promise.reject(error);
+  });
+
+function navigate_to_article(self, source) {
+  axios.get(api_endpoint + '/' + self.$route.params.lang + '/article/' + self.$route.params.id)
+  .then(function(response){
+    self.article = Object.assign(response.data, {'dictionary': self.$route.params.lang})
+    self.search_results = []
+  })
+  .catch(function(error){
+    if (error.response && error.response.status == 404) {
+      self.article = {
+        lemmas: [],
+        error: "Vi har ingen artikkel med id " + self.$route.params.id
+      }
+    } else {
+      self.article = {
+        lemmas: [],
+        error: "Noe gikk galt..."
+      }
+      console.log(error)
+    }
+  })
+  .then(function(response){
+    self.waiting_for_articles = false
+    history.replaceState({article: self.article, search_results: [], lang: self.lang}, '')
+    if (source) {
+      self.$plausible.trackEvent('internal link incoming', {props: {origin: source}})
+    }
+  })
+}
+
+function navigate_to_search(self, query) {
+  axios.get(self.api_pref + 'search?q=' + query)
+  .then(function(response){
+    self.search_results = response.data
+    if (! self.search_results.length) {
+      self.article = {
+        lemmas: [],
+        error: "Vi fant ingen resultater for '" + decodeURIComponent(query) + "'. (Søkeforlag kommer i en senere oppatering av Ordbøkene)"
+      }
+    }
+  })
+  .catch(function(error){
+    if (error.response && error.response.status == 400) {
+      self.article = {
+        lemmas: [],
+        error: "Søkeuttrykket inneholder feil"
+      }
+    } else if (error.response) {
+      self.article = {
+        lemmas: [],
+        error: "Noe gikk galt på serversiden"
+      }
+    } else {
+      self.article = {
+        lemmas: [],
+        error: "Nettverksproblemer, prøv igjen"
+      }
+    }
+  })
+  .then(function(_){
+    self.waiting_for_articles = false
+    history.replaceState({article: self.article, search_results: self.search_results, lang: self.lang}, '')
+  })
+}
+
+function navigate_to_word(self, word) {
+  axios.get(self.api_pref + 'suggest?q=' + word)
+  .then(function(response){
+    self.search_results = response.data.filter(result => result.match.length == word.length)
+    if (! self.search_results.length) {
+      self.article = {
+        lemmas: [],
+        error: "Ordet '" + decodeURIComponent(word) + "' finnes ikke i ordbøkene"
+      }
+    }
+  })
+  .catch(function(error){
+    if (error.response) {
+      self.article = {
+        lemmas: [],
+        error: "Noe gikk galt på serversiden"
+      }
+    } else {
+      self.article = {
+        lemmas: [],
+        error: "Nettverksproblemer, prøv igjen"
+      }
+    }
+  })
+  .then(function(_){
+    self.waiting_for_articles = false
+    history.replaceState({article: self.article, search_results: self.search_results, lang: self.lang}, '')
+  })
+}
+
+export default {
+  name: 'DictionaryView',
+  data: function() {
+    return {
+      article_key: 0,
+      search_results: [],
+      lang: 'bob,nob',
+      waiting_for_articles: true,
+      waiting_for_metadata: true,
+      article: {lemmas: [], body:{pronunciation: [], definitions: [], etymology: []}},
+      monthly_bm: {lemmas: [], body:{pronunciation: [], definitions: [], etymology: []}},
+      monthly_nn: {lemmas: [], body:{pronunciation: [], definitions: [], etymology: []}}
+    }
+  },
+  computed: {
+    waiting: function() {
+      return (this.waiting_for_articles || this.waiting_for_metadata) && this.$route.name != 'root'
+    },
+    api_pref: function() {
+      return api_endpoint + '/' + this.lang + '/article/'
+    }
+  },
+  components: {
+    Article,
+    Autocomplete,
+    SearchResults
+  },
+  methods: {
+    select_result: function(event) {
+      if(event.articles){
+        this.$router.push('/' + this.lang + '/w/' + event.word)
+        this.search_results = event.articles
+        this.article = {lemmas: [], body:{pronunciation: [], definitions: [], etymology: []}}
+        history.replaceState({article: this.article, search_results: this.search_results, lang: this.lang}, '')
+        this.$plausible.trackEvent('dropdown selection', {props: {query: event.label, match: event.match}})
+      }else{
+        this.waiting_for_articles = true
+        this.article = {lemmas: [], body:{pronunciation: [], definitions: [], etymology: []}}
+        this.$router.push(`/${this.lang}/search/${event.q}`)
+        navigate_to_search(this, event.q)
+        this.$plausible.trackEvent('dropdown selection', {props: {query: event.label, match: '<fritekstsøk>'}})
+      }
+    },
+    article_link_click: function(item) {
+      if (this.article.article_id == item.article_id){
+        this.article_key++
+        history.replaceState({article: this.article, search_results: this.search_results, lang: this.lang}, '')
+      }else{
+        this.article = {lemmas: [], body:{pronunciation: [], definitions: [], etymology: []}}
+        this.waiting_for_articles = true
+        navigate_to_article(this, item.source)
+      }
+    }
+  },
+  mounted: function(){
+    let self = this
+    this.lang = 'bob,nob'
+
+    Promise.all([
+      axios.get(api_endpoint + '/bob').then(function(response){
+        let concepts = response.data.concepts
+        entities.bob = concepts
+      }),
+      axios.get(api_endpoint + '/nob').then(function(response){
+        let concepts = response.data.concepts
+        entities.nob = concepts
+      })
+    ]).then(function(_) {
+      self.waiting_for_metadata = false
+      if(self.$route.name == 'word') {
+        self.lang = self.$route.params.lang
+        navigate_to_word(self, self.$route.params.word)
+      }
+      else if(self.$route.name == 'lookup'){
+        navigate_to_article(self, self.$route.params.id)
+      }
+      else if (self.$route.name == 'search') {
+        self.lang = self.$route.params.lang
+        navigate_to_search(self, self.$route.params.query)
+      }
+      else {
+        self.lang = self.$route.params.lang || 'bob,nob'
+        self.waiting_for_articles = false
+        history.replaceState({article: self.article, search_results: self.search_results, lang: self.lang}, '')
+      }
+
+      // words of the month
+      axios.get(api_endpoint + '/bob/article/5607').then(function(response){
+        self.monthly_bm = Object.assign(response.data, {dictionary: 'bob'})
+      })
+
+      axios.get(api_endpoint + '/nob/article/78569').then(function(response){
+        self.monthly_nn = Object.assign(response.data, {dictionary: 'nob'})
+      })
+    }).catch(function(_){
+      self.article = {
+        lemmas: [],
+        error: "Et nettverksproblem hindret lasting av siden. Prøv å laste siden på nytt"
+      }
+      self.waiting_for_metadata = false
+      self.waiting_for_articles = false
+    })
+  },
+  watch: {
+    $route() {
+      this.$plausible.trackEvent('language', {props: {code: this.$route.params.lang}})
+    }
+  },
+  created: function() {
+    let self = this
+    window.onpopstate = function (event) {
+      if (event.state) {
+        self.article = event.state.article
+        self.search_results = event.state.search_results
+        self.lang = event.state.lang
+      }
+    }
+
+  }
+}
+</script>
+
+<style>
+  @import url('https://fonts.googleapis.com/css2?family=Inria+Serif:ital,wght@0,400;0,700;1,400;1,700&display=swap');
+  @import url('https://fonts.googleapis.com/css2?family=Inria+Serif:ital,wght@0,400;0,700;1,400;1,700&family=Noto+Sans:ital,wght@0,400;0,700;1,400;1,700&display=swap');
+
+#app {
+  font-family: 'Noto Sans', Helvetica, Arial, sans-serif;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  color: #2c3e50;
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+}
+
+html, body {
+    height: 100%
+}
+
+body {
+  margin: 0px;
+
+}
+
+h1 {
+  font-family: Inria Serif;
+  font-size: 36px;
+  color: var(--v-primary-base);
+}
+
+
+header > h1 > a {
+  color: var(--v-secondary-base) !important;
+  font-size: 40px;
+  margin: 0px;
+  text-decoration: none;
+}
+
+p.about-link {
+  text-align: right;
+  margin: 0px;
+  float: right;
+}
+
+header > p > a {
+  color: var(--v-tertiary-base)   !important;
+  text-decoration: none;
+}
+
+span.beta {
+  color: #BBBBBB;
+}
+
+p.about-link > a{
+  text-decoration: none;
+  border-bottom: solid var(--v-secondary-base) 4px;
+  font-size: 12px;
+  color: var(--v-tertiary-base);
+}
+
+main {
+  padding-bottom: 20px;
+  flex: 1 0 auto;
+  background-color: var(--v-tertiary-base);
+}
+
+main.welcome {
+  background-image: url('../assets/books.jpg');
+  background-repeat: no-repeat;
+  background-attachment: fixed;
+}
+
+header, #search_results, #spinner, #single_article_container, footer, div.welcome, div.search_container {
+  padding-left: calc((100vw - 1000px) / 2);
+  padding-right: calc((100vw - 1000px) / 2);
+}
+
+@media (max-width: 500px) {
+  .verbose {
+    display: none;
+  }
+}
+
+#spinner {
+  padding-top: 40px;
+}
+
+header {
+  padding-top: 20px;
+  padding-bottom: 20px;
+  background-color: var(--v-primary-base);
+}
+
+div.monthly {
+  padding: 20px;
+  border-radius: 10px;
+  display: flex;
+  width: 100%;
+}
+
+div.monthly > div {
+  flex: 50%;
+}
+
+div.monthly article.bob .dict-label::before {
+  content: "månedens ";
+}
+
+div.monthly article.nob .dict-label::before {
+  content: "månadens ";
+}
+
+.sub-title {
+  font-size: 20px;
+  margin: 0px;
+}
+
+footer {
+  font-size: smaller;
+  display: table;
+  flex-direction: row;
+  background-color: var(--v-primary-base);
+  color: #ffffff;
+}
+
+.search_container {
+  max-width: 1400px;
+  padding-top: 50px;
+}
+
+.v-label span {
+  color: var(--v-primary-base);
+}
+
+li.suggestion {
+  font-weight: bold;
+  padding-left: 20px;
+  padding-top: 5px;
+  padding-bottom: 5px;
+  border: 0px;
+  background-image: none;
+}
+
+footer > div {
+  display: table-cell;
+  vertical-align: middle;
+  padding: 10px;
+}
+
+#srlogo {
+  height: 20px;
+}
+
+#uiblogo {
+  height: 60px;
+}
+
+::selection {
+  background: var(--v-secondary-base);
+  color: white;
+}
+
+</style>
diff --git a/src/main.js b/src/main.js
index d91dda067e037b0b14d2c28805e67eff629d9452..6744e108942d7e6bf3bb030cb8b8c20de167660d 100644
--- a/src/main.js
+++ b/src/main.js
@@ -1,5 +1,8 @@
 import Vue from 'vue'
+import Root from './Root.vue'
 import App from './App.vue'
+import About from './components/About.vue'
+import DictionaryView from './components/DictionaryView.vue'
 import VueRouter from 'vue-router'
 import { VuePlausible } from 'vue-plausible'
 import vuetify from './plugins/vuetify';
@@ -17,28 +20,44 @@ const router = new VueRouter({
   base: __dirname,
   routes: [
     {
-      name: 'root',
-      path: '/:lang?',
-      component: App }, // No props, no nothing
-    {
-      name: 'word',
-      path: '/:lang/w/:word'
-    },
-    {
-      name: 'lookup',
-      path: '/:lang/:id(\\d+)/:lemma?',
-      component: App,
-      props: true }, // Pass route.params to props
-    {
-      name: 'search',
-      path: '/:lang/search/:query',
+      path: '/',
       component: App,
-      props: true}
+      children: [
+        {
+          path: 'om',
+          name: 'about',
+          component: About
+        },
+        {
+          path: '',
+          component: DictionaryView,
+          children: [
+            {
+              path: ':lang',
+              children: [
+                {
+                  name: 'word',
+                  path: 'w/:word'
+                },
+                {
+                  name: 'lookup',
+                  path: ':id(\\d+)/:lemma?'
+                },
+                {
+                  name: 'search',
+                  path: 'search/:query'
+                }
+              ]
+            }
+          ]
+        }
+      ]
+    }
   ]
 })
 
 new Vue({
   router,
   vuetify,
-  render: h => h(App)
+  render: h => h(Root )
 }).$mount('#app')
diff --git a/vue.config.js b/vue.config.js
index 2ae460b7a5a079f11a2e4db80dfa46d12b5a101d..650ea49f0f51e5f12e062b0332cd204fbe2e7d65 100644
--- a/vue.config.js
+++ b/vue.config.js
@@ -1,4 +1,5 @@
 module.exports = {
+  runtimeCompiler: true,
   transpileDependencies: [
     'vuetify'
   ]