diff --git a/.gitignore b/.gitignore
index e439b3942f01da7b04f388ef70d73be6150385cb..4346d5327e01732cf9652dc050b290cbf10e39da 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,5 @@
 /vendor/
 /node_modules/
 /cli/
+/assets/css
 /imghost
diff --git a/assets/js/page_image_detail.js b/assets/js/page_image_detail.js
index 0bca43c9dd84a479b28b0721f1847aa9c91fce4b..8b992af8c1109693628eaaeba3d3a696a9244737 100644
--- a/assets/js/page_image_detail.js
+++ b/assets/js/page_image_detail.js
@@ -1,14 +1,3 @@
-function postData(url, data) {
-    return fetch(url, {
-        body: data,
-        cache: 'no-cache',
-        credentials: 'same-origin',
-        method: 'POST',
-        mode: 'cors',
-        redirect: 'follow'
-    }).then(response => response.json())
-}
-
 const fakeTitle = document.querySelector(".title.fake-input[contenteditable]");
 const fakeDescription = document.querySelector(".description.fake-input[contenteditable]");
 
@@ -22,16 +11,24 @@ let lastTimeOut = null;
 let hasChanged = false;
 let isSaving = false;
 
-const doSave = () => {
-    const data = new FormData(document.forms.namedItem("upload"));
-    data.append("from_js", "true");
-    save.value = "Saving…";
-    hasChanged = false;
-    isSaving = true;
-    postData(location.href, data).then((json) => {
-        save.value = "Saved";
-        isSaving = false;
-    })
+const doSave = async () => {
+  const data = new FormData(document.forms.namedItem("upload"));
+  data.append("from_js", "true");
+  save.value = "Saving…";
+  hasChanged = false;
+  isSaving = true;
+  const response = await fetch(location.href, {
+    body: data,
+    cache: 'no-cache',
+    credentials: 'same-origin',
+    method: 'POST',
+    mode: 'cors',
+    redirect: 'follow'
+  });
+  if (response.ok) {
+    save.value = "Saved";
+    isSaving = false;
+  }
 };
 
 const scheduleSave = () => {
@@ -92,4 +89,4 @@ window.addEventListener("beforeunload", (e) => {
         e.returnValue = message;
         return message;
     }
-});
\ No newline at end of file
+});
diff --git a/assets/js/page_upload.js b/assets/js/page_upload.js
index 33b5b67108f69d1b5c0b144e0f34680c8cc17a58..d656d2495f5b76e3d8f0928f069df8a04ec8a9fb 100644
--- a/assets/js/page_upload.js
+++ b/assets/js/page_upload.js
@@ -1,24 +1,11 @@
-function postData(url, data) {
-    return fetch(url, {
-        body: data,
-        cache: 'no-cache',
-        credentials: 'same-origin',
-        method: 'POST',
-        mode: 'cors',
-        redirect: 'follow'
-    }).then(response => response.json())
-}
-
 const page = document.querySelector(".page.upload");
-const form = document.querySelector("form.upload");
 const element = document.querySelector("form.upload input[type=file]");
 const results = document.querySelector(".uploading-images .images");
-const sidebar = document.querySelector(".uploading-images .sidebar");
 element.addEventListener("change", () => {
     page.classList.add("submitted");
     for (let file of element.files) {
         const reader = new FileReader();
-        reader.addEventListener("load", (e) => {
+        reader.addEventListener("load", async (e) => {
             const dataUrl = e.target.result;
 
             const image_container = document.createElement("div");
@@ -61,21 +48,26 @@ element.addEventListener("change", () => {
             const data = new FormData();
             data.append("file", file, file.name);
 
-            postData("/upload/", data).then((json) => {
-                image_container.classList.remove("uploading");
-                if (json.success) {
-                    image_link.href = "/" + json.id + ".png";
-                    image.src = "/" + json.id + ".png";
-                } else {
-                    const image_error = document.createElement("div");
-                    image_error.classList.add("alert", "error");
-                    image_error.innerText = JSON.stringify(json.errors);
-                    image_container.insertBefore(image_error, image_description);
-                }
-
-                console.log(json);
+            const response = await fetch("/upload/", {
+                body: data,
+                cache: 'no-cache',
+                credentials: 'same-origin',
+                method: 'POST',
+                mode: 'cors',
+                redirect: 'follow'
             });
+            image_container.classList.remove("uploading");
+            if (response.ok) {
+                image_link.href = "/" + json.id + ".png";
+                image.src = "/" + json.id + ".png";
+            } else {
+                const image_error = document.createElement("div");
+                image_error.classList.add("alert", "error");
+                image_error.innerText = JSON.stringify(json.errors);
+                image_container.insertBefore(image_error, image_description);
+                console.log(json);
+            }
         });
         reader.readAsDataURL(file);
     }
-});
\ No newline at end of file
+});
diff --git a/assets/sass/_navigation.sass b/assets/sass/_navigation.sass
index a0e18dd133e9dde54a1e82934f3b1a1bdbbd8019..191378da92c5ce48404bd40ea7d0bbe1e4cec595 100644
--- a/assets/sass/_navigation.sass
+++ b/assets/sass/_navigation.sass
@@ -1,4 +1,4 @@
-nav
+nav.navigation
   position: sticky
   top: 0
   background: #333333
@@ -11,12 +11,8 @@ nav
     margin: 0 auto
     height: 56px
     align-items: center
-    li, li > *
-      color: #282828
-      text-decoration: none
     li
       display: block
-      line-height: 24px
       &.images, &.albums
         @media (max-width: 640px)
           display: none
@@ -36,12 +32,3 @@ nav
           margin-left: 0
         &:last-child
           margin-right: 0
-        &:not(.title) a
-          display: block
-          background: #FFC107
-          padding: 4px 16px
-          border-radius: 2px
-          box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1)
-          &:hover, &:focus
-            background: #FFD54F
-            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2)
\ No newline at end of file
diff --git a/assets/sass/_page_image_list.sass b/assets/sass/_page_image_list.sass
index 3dc8cfcd82f3c019f35df643acdbaff8c1793881..2a9121068587294ba180a9ce968cdfb06e2c58ef 100644
--- a/assets/sass/_page_image_list.sass
+++ b/assets/sass/_page_image_list.sass
@@ -1,32 +1,37 @@
 .page.image.list
-  display: flex
   max-width: 1024px
-  margin: 0 auto
-  align-items: start
-  flex-wrap: wrap
+  margin: 8px auto 48px
+  display: grid
+  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr))
+  grid-gap: 8px
+  align-self: stretch
+  width: calc(100% - 16px)
+
   .image
     padding: 8px
-    margin: 8px
     position: relative
     box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2)
     transition: all 200ms
     text-decoration: none
-    width: 160px
     background: #333333
     border-radius: 2px
+    will-change: transform
+
     &:hover, &:focus
-      margin-top: 4px
+      transform: translate(0, 4px)
       box-shadow: 0 4px 6px rgba(0, 0, 0, 0.4)
     .image-container
       display: flex
-      justify-content: center
-      align-content: center
-      justify-items: center
-      align-items: center
+      justify-content: stretch
+      align-content: stretch
+      justify-items: stretch
+      align-items: stretch
       flex-direction: column
-      height: 160px
-      width: 160px
       background: #000000
+
+      img
+        aspect-ratio: 1
+        object-fit: contain
     .info
       display: block
       z-index: 1
@@ -34,7 +39,14 @@
       line-height: 1.25
       font-size: 10pt
       padding-top: 12px
+
       p
         white-space: nowrap
         text-overflow: ellipsis
-        overflow: hidden
\ No newline at end of file
+        overflow: hidden
+
+        &.title
+          font-weight: 600
+
+          span.placeholder
+            opacity: 0.4
diff --git a/assets/sass/_pagination.sass b/assets/sass/_pagination.sass
new file mode 100644
index 0000000000000000000000000000000000000000..c0d17333f58c8778ba641eeb3ae8b966d2d3ee60
--- /dev/null
+++ b/assets/sass/_pagination.sass
@@ -0,0 +1,24 @@
+ul.pagination
+  display: flex
+  flex-direction: row
+  background: #333
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2)
+  padding: 4px 8px
+  color: #fff
+  justify-content: space-between
+  grid-column-start: 1
+  grid-column-end: -1
+  max-width: 480px
+  justify-self: center
+  width: 100%
+
+  li.page
+    appearance: none
+    list-style: none
+    margin: 4px
+    padding: 0
+    line-height: 24px
+
+    &.current
+      display: block
+      padding: 4px 16px
diff --git a/assets/sass/style.sass b/assets/sass/style.sass
index 1017499e7ad1f284b1bd3675d4ff8b082baaec8d..3a4d9d5828a355ef719627715ad1a4dd5635641b 100644
--- a/assets/sass/style.sass
+++ b/assets/sass/style.sass
@@ -29,7 +29,28 @@ body
   width: 100%
   max-width: 640px
 
+.button
+  display: block
+  background: #FFC107
+  padding: 4px 16px
+  border-radius: 2px
+  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1)
+  color: #282828
+  text-decoration: none
+  line-height: 24px
+  cursor: pointer
+
+  &:hover, &:focus
+    background: #FFD54F
+    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2)
+
+  &[aria-disabled=true], &:disabled
+    cursor: default
+    background: #838383
+    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.1)
+
 @import "navigation"
 @import "page_upload"
 @import "page_image_detail"
 @import "page_image_list"
+@import "pagination"
diff --git a/errors.go b/errors.go
new file mode 100644
index 0000000000000000000000000000000000000000..29c96ff8fddb13183833358a260bb30bed16fca8
--- /dev/null
+++ b/errors.go
@@ -0,0 +1,47 @@
+package main
+
+import (
+	"log"
+	"net/http"
+	"net/url"
+)
+
+type ErrorData struct {
+	Code  int
+	User  UserInfo
+	URL   *url.URL
+	Error error
+}
+
+type errorDto struct {
+	Path  string `json:"path"`
+	Error string `json:"error"`
+}
+
+func formatError(w http.ResponseWriter, data ErrorData, format string) {
+	if data.Code != 0 {
+		data.Code = 500
+	}
+	log.Printf(
+		"A type %d error occured for user %s while accessing %s: %s",
+		data.Code,
+		data.User.Id,
+		data.URL.Path,
+		data.Error.Error(),
+	)
+	w.WriteHeader(data.Code)
+	if format == "html" {
+		if err := formatTemplate(w, "error.html", data); err != nil {
+			log.Printf("Error while serving html error for %s", data.URL.Path)
+			return
+		}
+	} else if format == "json" {
+		if err := returnJson(w, errorDto{
+			data.URL.Path,
+			data.Error.Error(),
+		}); err != nil {
+			log.Printf("Error while serving json error for %s", data.URL.Path)
+			return
+		}
+	}
+}
diff --git a/page_image_detail.go b/page_image_detail.go
index 2cc76cf2dfdeb02b455a3aca1f274673d3885d8c..c0fa94f7751a169dd1dd40ac1c736162bbd1777d 100644
--- a/page_image_detail.go
+++ b/page_image_detail.go
@@ -32,8 +32,7 @@ func pageImageDetail(ctx PageContext) http.Handler {
 			WHERE id = $1
 			`, imageId)
 		if err != nil {
-			fmt.Printf("An error occured: %s", err.Error())
-			_ = returnError(w, http.StatusInternalServerError, "Internal Server Error")
+			formatError(w, ErrorData{500, user, r.URL, err}, "html")
 			return
 		}
 
@@ -41,45 +40,40 @@ func pageImageDetail(ctx PageContext) http.Handler {
 
 		if result.Next() {
 			var owner string
-			err := result.Scan(&info.Id, &owner, &info.Title, &info.Description, &info.CreatedAt, &info.OriginalName, &info.MimeType)
-			if err != nil {
-				fmt.Printf("An error occured: %s", err.Error())
-				_ = returnError(w, http.StatusInternalServerError, "Internal Server Error")
+			if err := result.Scan(&info.Id, &owner, &info.Title, &info.Description, &info.CreatedAt, &info.OriginalName, &info.MimeType); err != nil {
+				formatError(w, ErrorData{500, user, r.URL, err}, "html")
 				return
 			}
 
 			switch r.PostFormValue("action") {
 			case "update":
-				_, err = ctx.Database.Exec(
+				if _, err := ctx.Database.Exec(
 					"UPDATE images SET title = $1, description = $2 WHERE id = $3 AND owner = $4",
 					r.PostFormValue("title"),
 					r.PostFormValue("description"),
 					info.Id,
 					user.Id,
-				)
-				if err != nil {
-					fmt.Printf("An error occured: %s", err.Error())
-					_ = returnError(w, http.StatusInternalServerError, "Internal Server Error")
+				); err != nil {
+					formatError(w, ErrorData{500, user, r.URL, err}, "html")
 					return
 				}
 				if r.PostFormValue("from_js") == "true" {
-					_ = returnJson(w, true)
+					if err := returnJson(w, true); err != nil {
+						formatError(w, ErrorData{500, user, r.URL, err}, "html")
+						return
+					}
 				} else {
 					http.Redirect(w, r, r.URL.Path, http.StatusFound)
 				}
 				return
 			case "delete":
-				_, err = ctx.Database.Exec("DELETE FROM images WHERE id = $1 AND owner = $2", info.Id, user.Id)
-				if err != nil {
-					fmt.Printf("An error occured: %s", err.Error())
-					_ = returnError(w, http.StatusInternalServerError, "Internal Server Error")
+				if _, err := ctx.Database.Exec("DELETE FROM images WHERE id = $1 AND owner = $2", info.Id, user.Id); err != nil {
+					formatError(w, ErrorData{500, user, r.URL, err}, "html")
 					return
 				}
 				for _, definition := range ctx.Config.Sizes {
-					err := os.Remove(path.Join(ctx.Config.TargetFolder, fmt.Sprintf("%s%s", info.Id, definition.Suffix)))
-					if err != nil && !os.IsNotExist(err) {
-						fmt.Printf("An error occured: %s", err.Error())
-						_ = returnError(w, http.StatusInternalServerError, "Internal Server Error")
+					if err := os.Remove(path.Join(ctx.Config.TargetFolder, fmt.Sprintf("%s%s", info.Id, definition.Suffix))); err != nil {
+						formatError(w, ErrorData{500, user, r.URL, err}, "html")
 						return
 					}
 				}
@@ -92,11 +86,15 @@ func pageImageDetail(ctx PageContext) http.Handler {
 				info,
 				owner == user.Id,
 			}); err != nil {
-				panic(err)
+				formatError(w, ErrorData{500, user, r.URL, err}, "html")
+				return
 			}
 			return
 		}
 
-		_ = returnError(w, http.StatusNotFound, "Image Not Found")
+		if err := returnError(w, http.StatusNotFound, "Image Not Found"); err != nil {
+			formatError(w, ErrorData{500, user, r.URL, err}, "html")
+			return
+		}
 	})
 }
diff --git a/page_image_list.go b/page_image_list.go
index fb1950d28556cd8d460155312ee557bc3de6155b..462cb6aad05b693eae5f8db41f07838da5594a73 100644
--- a/page_image_list.go
+++ b/page_image_list.go
@@ -1,19 +1,39 @@
 package main
 
 import (
+	"database/sql"
 	"net/http"
+	"path"
+	"strconv"
 )
 
 type ImageListData struct {
-	User   UserInfo
-	Images []Image
+	User     UserInfo
+	Images   []Image
+	Previous int64
+	Current  int64
+	Next     int64
 }
 
-func pageImageList(ctx PageContext) http.Handler {
-	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		user := parseUser(r)
+const PageSize = 30
 
-		result, err := ctx.Database.Query(`
+func paginateImageListQuery(ctx PageContext, user UserInfo, offset int64, pageSize int) (*sql.Rows, error) {
+	if offset == 0 {
+		return ctx.Database.Query(`
+			SELECT
+				id,
+				coalesce(title,  ''),
+				coalesce(description, ''),
+        		coalesce(created_at, to_timestamp(0)),
+				coalesce(original_name, ''),
+				coalesce(type, '')
+			FROM images
+			WHERE owner = $1
+			ORDER BY created_at DESC
+			LIMIT $2
+		`, user.Id, pageSize)
+	} else {
+		return ctx.Database.Query(`
 			SELECT
 				id,
 				coalesce(title,  ''),
@@ -24,17 +44,39 @@ func pageImageList(ctx PageContext) http.Handler {
 			FROM images
 			WHERE owner = $1
 			ORDER BY created_at DESC
-			`, user.Id)
+			LIMIT $3
+			OFFSET $2
+		`, user.Id, offset, pageSize)
+	}
+}
+
+func pageImageList(ctx PageContext) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		user := parseUser(r)
+		_, page := path.Split(r.URL.Path)
+		var pageNumber int64
+		pageNumber, err := strconv.ParseInt(page, 10, 64)
+		if err != nil {
+			pageNumber = 1
+		}
+
+		result, err := paginateImageListQuery(
+			ctx,
+			user,
+			(pageNumber-1)*PageSize,
+			PageSize,
+		)
 		if err != nil {
-			panic(err)
+			formatError(w, ErrorData{500, user, r.URL, err}, "html")
+			return
 		}
 
 		var images []Image
 		for result.Next() {
 			var info Image
-			err := result.Scan(&info.Id, &info.Title, &info.Description, &info.CreatedAt, &info.OriginalName, &info.MimeType)
-			if err != nil {
-				panic(err)
+			if err := result.Scan(&info.Id, &info.Title, &info.Description, &info.CreatedAt, &info.OriginalName, &info.MimeType); err != nil {
+				formatError(w, ErrorData{500, user, r.URL, err}, "html")
+				return
 			}
 			images = append(images, info)
 		}
@@ -42,8 +84,12 @@ func pageImageList(ctx PageContext) http.Handler {
 		if err = formatTemplate(w, "image_list.html", ImageListData{
 			user,
 			images,
+			pageNumber - 1,
+			pageNumber,
+			pageNumber + 1,
 		}); err != nil {
-			panic(err)
+			formatError(w, ErrorData{500, user, r.URL, err}, "html")
+			return
 		}
 	})
 }
diff --git a/page_index.go b/page_index.go
index a2052fddfec801e1564bee2f6701aed0d165372b..cfc40dd5124eab93f3758aebadbda997e53527ce 100644
--- a/page_index.go
+++ b/page_index.go
@@ -22,15 +22,15 @@ func pageIndex(ctx PageContext) http.Handler {
 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		if r.URL.Path == "/" {
 			http.Redirect(w, r, "/me/images", http.StatusFound)
-        } else if r.URL.Path == "/favicon.ico" {
+		} else if r.URL.Path == "/favicon.ico" {
 			w.Header().Set("Vary", "Accept-Encoding")
 			w.Header().Set("Cache-Control", "public, max-age=31536000")
 			ctx.AssetServer.ServeHTTP(w, r)
-        } else if r.URL.Path == "/favicon.png" {
+		} else if r.URL.Path == "/favicon.png" {
 			w.Header().Set("Vary", "Accept-Encoding")
 			w.Header().Set("Cache-Control", "public, max-age=31536000")
 			ctx.AssetServer.ServeHTTP(w, r)
-        } else if r.URL.Path == "/favicon.svg" {
+		} else if r.URL.Path == "/favicon.svg" {
 			w.Header().Set("Vary", "Accept-Encoding")
 			w.Header().Set("Cache-Control", "public, max-age=31536000")
 			ctx.AssetServer.ServeHTTP(w, r)
diff --git a/page_upload.go b/page_upload.go
index 4cca18df92d500457537fc6a06f7d8d86f9c8ef1..b5f8a0dd8db008ff51ba47fbab5b5a785d3413a9 100644
--- a/page_upload.go
+++ b/page_upload.go
@@ -13,11 +13,6 @@ import (
 	"time"
 )
 
-type UploadData struct {
-	User    UserInfo
-	Results []Result
-}
-
 func detectMimeType(path string) (string, error) {
 	file, err := os.Open(path)
 	if err != nil {
@@ -84,49 +79,30 @@ func pageUpload(ctx PageContext) http.Handler {
 
 			err := r.ParseMultipartForm(32 << 20)
 			if err != nil {
-				if err = returnJson(w, []Result{{
-					Success: false,
-					Errors:  []string{err.Error()},
-				}}); err != nil {
-					panic(err)
-				}
+				formatError(w, ErrorData{500, user, r.URL, err}, "json")
+				return
 			}
 
 			file, header, err := r.FormFile("file")
 			if err != nil {
-				if err = returnJson(w, []Result{{
-					Success: false,
-					Errors:  []string{err.Error()},
-				}}); err != nil {
-					panic(err)
-				}
+				formatError(w, ErrorData{500, user, r.URL, err}, "json")
 				return
 			}
 			image, err := createImage(ctx.Config, file, header)
 			if err != nil {
-				if err = returnJson(w, []Result{{
-					Success: false,
-					Errors:  []string{err.Error()},
-				}}); err != nil {
-					panic(err)
-				}
+				formatError(w, ErrorData{500, user, r.URL, err}, "json")
 				return
 			}
 
 			pubsub := ctx.Redis.Subscribe(ctx.Context, ctx.Config.ResultChannel)
-			_, err = ctx.Database.Exec("INSERT INTO images (id, owner, created_at, updated_at, original_name, type) VALUES ($1, $2, $3, $4, $5, $6)", image.Id, user.Id, image.CreatedAt, image.CreatedAt, image.OriginalName, image.MimeType)
-			if err != nil {
-				panic(err)
+			if _, err = ctx.Database.Exec("INSERT INTO images (id, owner, created_at, updated_at, original_name, type) VALUES ($1, $2, $3, $4, $5, $6)", image.Id, user.Id, image.CreatedAt, image.CreatedAt, image.OriginalName, image.MimeType); err != nil {
+				formatError(w, ErrorData{500, user, r.URL, err}, "json")
+				return
 			}
 
 			data, err := json.Marshal(image)
 			if err != nil {
-				if err = returnJson(w, []Result{{
-					Success: false,
-					Errors:  []string{err.Error()},
-				}}); err != nil {
-					panic(err)
-				}
+				formatError(w, ErrorData{500, user, r.URL, err}, "json")
 				return
 			}
 
@@ -138,24 +114,14 @@ func pageUpload(ctx PageContext) http.Handler {
 			for waiting {
 				message, err := pubsub.ReceiveMessage(ctx.Context)
 				if err != nil {
-					if err = returnJson(w, []Result{{
-						Success: false,
-						Errors:  []string{err.Error()},
-					}}); err != nil {
-						panic(err)
-					}
+					formatError(w, ErrorData{500, user, r.URL, err}, "json")
 					return
 				}
 
 				result := Result{}
 				err = json.Unmarshal([]byte(message.Payload), &result)
 				if err != nil {
-					if err = returnJson(w, []Result{{
-						Success: false,
-						Errors:  []string{err.Error()},
-					}}); err != nil {
-						panic(err)
-					}
+					formatError(w, ErrorData{500, user, r.URL, err}, "json")
 					return
 				}
 
@@ -165,18 +131,19 @@ func pageUpload(ctx PageContext) http.Handler {
 					waiting = false
 
 					if err = returnJson(w, result); err != nil {
-						panic(err)
+						formatError(w, ErrorData{500, user, r.URL, err}, "json")
+						return
 					}
 				}
 			}
 			return
 		} else {
 			user := parseUser(r)
-			if err := formatTemplate(w, "upload.html", UploadData{
+			if err := formatTemplate(w, "upload.html", IndexData{
 				user,
-				[]Result{},
 			}); err != nil {
-				panic(err)
+				formatError(w, ErrorData{500, user, r.URL, err}, "html")
+				return
 			}
 		}
 	})
diff --git a/templates/_base.html b/templates/_base.html
index 9bab238305238fd1fd8e0ee8b9228975c01f09d8..32daf1d189297bc6b2ef3d840afacbeae0c51e75 100644
--- a/templates/_base.html
+++ b/templates/_base.html
@@ -1,4 +1,5 @@
+{{- /*gotype: git.kuschku.de/justjanne/imghost-frontend.IndexData*/ -}}
 {{template "header" .}}
 {{template "navigation" .User}}
 {{template "content" .}}
-{{template "footer" .}}
\ No newline at end of file
+{{template "footer" .}}
diff --git a/templates/_footer.html b/templates/_footer.html
index 18fdea37de06501ac0557fb12a25d41db1d9baef..fe5d3d9bba06cc55f06009529442604d97ae461b 100644
--- a/templates/_footer.html
+++ b/templates/_footer.html
@@ -1,4 +1,5 @@
+{{- /*gotype: git.kuschku.de/justjanne/imghost-frontend.IndexData*/ -}}
 {{define "footer"}}
 <script src="/assets/js/component/fake-input.js"></script>
 <script src="/assets/js/component/copy.js"></script>
-{{end}}
\ No newline at end of file
+{{end}}
diff --git a/templates/_header.html b/templates/_header.html
index 90a099ff9f019236c4c477a7dc967ffca516fa78..fc105fae6ca32d790df7553bafc81a20a4d136d8 100644
--- a/templates/_header.html
+++ b/templates/_header.html
@@ -1,3 +1,4 @@
+{{- /*gotype: git.kuschku.de/justjanne/imghost-frontend.IndexData*/ -}}
 {{define "header"}}
 <!DOCTYPE html>
 <meta charset="utf-8">
@@ -22,4 +23,4 @@
 
 <link href="/assets/css/style.css" rel="stylesheet">
 <link href="/assets/css/fonts.css" rel="stylesheet">
-{{end}}
\ No newline at end of file
+{{end}}
diff --git a/templates/_navigation.html b/templates/_navigation.html
index bca2fc8079fe271bea5ec416b956e05ee73352e4..698b784bc2a8a2f0953e99ae4f9cc8118de0000e 100644
--- a/templates/_navigation.html
+++ b/templates/_navigation.html
@@ -1,20 +1,21 @@
+{{- /*gotype: git.kuschku.de/justjanne/imghost-frontend.IndexData*/ -}}
 {{define "navigation"}}
-<nav>
-    <ul>
-        <li class="title"><a href="/"><img src="/assets/images/logo.svg"></a></li>
-    {{if .HasRole "imghost:user" }}
-        <li><a href="/upload">Upload</a></li>
-    {{end}}
-        <li class="spacer"></li>
-    {{if .HasRole "imghost:user" }}
-        <li class="images"><a href="/me/images">My Images</a></li>
-        <li class="albums"><a href="/me/albums">My Albums</a></li>
-        <li class="me"><a href="https://accounts.kuschku.de/profile">{{.Name}}</a></li>
-    {{else if .Id }}
-        <li class="me"><a href="https://accounts.kuschku.de/profile">{{.Name}}</a></li>
-    {{else}}
-        <li><a href="/me/images">Login</a></li>
-    {{end}}
-    </ul>
+    <nav class="navigation">
+        <ul>
+            <li class="title"><a href="/"><img src="/assets/images/logo.svg"></a></li>
+            {{if .HasRole "imghost:user" }}
+                <li><a class="button" href="/upload">Upload</a></li>
+            {{end}}
+            <li class="spacer"></li>
+            {{if .HasRole "imghost:user" }}
+                <li class="images"><a class="button" href="/me/images">My Images</a></li>
+                <li class="albums"><a class="button" href="/me/albums">My Albums</a></li>
+                <li class="me"><a class="button" href="https://accounts.kuschku.de/profile">{{.Name}}</a></li>
+            {{else if .Id }}
+                <li class="me"><a class="button" href="https://accounts.kuschku.de/profile">{{.Name}}</a></li>
+            {{else}}
+                <li><a class="button" href="/me/images">Login</a></li>
+            {{end}}
+        </ul>
 </nav>
-{{end}}
\ No newline at end of file
+{{end}}
diff --git a/templates/error.html b/templates/error.html
new file mode 100644
index 0000000000000000000000000000000000000000..43279f66ccbae51d34c8aa946a92fec238b14192
--- /dev/null
+++ b/templates/error.html
@@ -0,0 +1,18 @@
+{{- /*gotype: git.kuschku.de/justjanne/imghost-frontend.ErrorData*/ -}}
+{{define "title"}}Error | ik8r{{end}}
+{{define "content"}}
+  <div class="page">
+    <div class="container centered">
+      <p>
+        <b>{{.Code}}.</b>
+        <ins>That’s an error.</ins>
+      </p>
+      <p>
+        An error occured while trying to access <code>{{.URL}}</code>.<br>
+        <ins>Sorry about that.</ins>
+
+        <code>{{.Error}}</code>
+      </p>
+    </div>
+  </div>
+{{end}}
diff --git a/templates/image_detail.html b/templates/image_detail.html
index abd530b355be7d0e29a4080e67b9a9893146bbde..280d20d99c0d2832bbb190c172e44578a6387d92 100644
--- a/templates/image_detail.html
+++ b/templates/image_detail.html
@@ -1,54 +1,55 @@
+{{- /*gotype: git.kuschku.de/justjanne/imghost-frontend.ImageDetailData*/ -}}
 {{define "title"}}{{.Image.Title}} | ik8r{{end}}
 {{define "content"}}
 <div class="page image detail">
-    <div class="detail">
-    {{if .IsMine}}
+  <div class="detail">
+      {{if .IsMine}}
         <h2 class="title fake-input" contenteditable="true" placeholder="Title">{{.Image.Title}}</h2>
-    {{else}}
+      {{else}}
         <h2 class="title">{{.Image.Title}}</h2>
-    {{end}}
-        <a class="image" href="/{{.Image.Id}}.png">
-            <img src="/{{.Image.Id}}.png">
-        </a>
-    {{if .IsMine}}
+      {{end}}
+    <a class="image" href="/{{.Image.Id}}.png">
+      <img src="/{{.Image.Id}}.png">
+    </a>
+      {{if .IsMine}}
         <p class="description fake-input" contenteditable="true" placeholder="Description"
            data-multiline>{{.Image.Description}}</p>
-    {{else}}
+      {{else}}
         <div class="description">{{.Image.Description}}</div>
-    {{end}}
-    </div>
-    <div class="sidebar">
-    {{if .IsMine}}
+      {{end}}
+  </div>
+  <div class="sidebar">
+      {{if .IsMine}}
         <div class="actions">
-            <form name="delete" class="delete-form" method="post">
-                <input type="hidden" name="action" value="delete">
-                <input type="hidden" name="id" value="{{.Image.Id}}">
-                <input type="submit" value="Delete">
-            </form>
-            <form name="upload" class="update-form" method="post">
-                <input type="hidden" name="action" value="update">
-                <input type="hidden" name="id" value="{{.Image.Id}}">
-                <input type="hidden" name="title" value="{{.Image.Title}}">
-                <input type="hidden" name="description" value="{{.Image.Description}}">
-                <input type="submit" id="save" value="Save">
-            </form>
-        </div>
-    {{end}}
-        <div class="url">
-            <p>Detail Page</p>
-            <div>
-                <input id="url_full" type="text" value="https://i.k8r.eu/i/{{.Image.Id}}">
-                <button class="copy" data-target="#url_full">Copy</button>
-            </div>
-        </div>
-        <div class="url">
-            <p>Direct Link</p>
-            <div>
-                <input id="url_direct" type="text" value="https://i.k8r.eu/{{.Image.Id}}.png">
-                <button class="copy" data-target="#url_direct">Copy</button>
-            </div>
+          <form name="delete" class="delete-form" method="post">
+            <input type="hidden" name="action" value="delete">
+            <input type="hidden" name="id" value="{{.Image.Id}}">
+            <input type="submit" value="Delete">
+          </form>
+          <form name="upload" class="update-form" method="post">
+            <input type="hidden" name="action" value="update">
+            <input type="hidden" name="id" value="{{.Image.Id}}">
+            <input type="hidden" name="title" value="{{.Image.Title}}">
+            <input type="hidden" name="description" value="{{.Image.Description}}">
+            <input type="submit" id="save" value="Save">
+          </form>
         </div>
+      {{end}}
+    <div class="url">
+      <p>Detail Page</p>
+      <div>
+        <input id="url_full" type="text" value="https://i.k8r.eu/i/{{.Image.Id}}">
+        <button class="copy" data-target="#url_full">Copy</button>
+      </div>
+    </div>
+    <div class="url">
+      <p>Direct Link</p>
+      <div>
+        <input id="url_direct" type="text" value="https://i.k8r.eu/{{.Image.Id}}.png">
+        <button class="copy" data-target="#url_direct">Copy</button>
+      </div>
     </div>
+  </div>
 </div>
 {{if .IsMine}}
 <script src="/assets/js/page_image_detail.js"></script>
diff --git a/templates/image_list.html b/templates/image_list.html
index 4cc941a2d5ca4a69695ceb5f558f31b07c3ee6bb..5f8383380e3493229eba5b8291bece60ca107975 100644
--- a/templates/image_list.html
+++ b/templates/image_list.html
@@ -1,12 +1,20 @@
+{{- /*gotype: git.kuschku.de/justjanne/imghost-frontend.ImageListData*/ -}}
 {{define "title"}}My Images | ik8r{{end}}
 {{define "content"}}
 <div class="page image list">
 {{range .Images}}
     <a class="image" href="/i/{{.Id}}">
         <div class="image-container">
-            <img src="/{{.Id}}t.png">
+            <img src="https://i.k8r.eu/{{.Id}}t.png">
         </div>
         <div class="info">
+            <p class="title">
+                {{- if eq .Title "" -}}
+                    <span class="placeholder">Unnamed</span>
+                {{- else -}}
+                    {{.Title}}
+                {{- end -}}
+            </p>
             <p>{{.OriginalName}}</p>
             <p>
                 <time>{{.CreatedAt.Format "2006-01-02 15:04"}}</time>
@@ -15,5 +23,18 @@
         </div>
     </a>
 {{end}}
+    <ul class="pagination">
+        <li class="page">
+            {{- if lt 0 .Previous -}}
+                <a class="button" href="/me/images/{{- .Previous -}}">Previous page</a>
+            {{- else -}}
+                <a class="button" aria-disabled="true">Previous page</a>
+            {{- end -}}
+        </li>
+        <li class="page current">Page {{ .Current }}</li>
+        <li class="page">
+            <a class="button" href="/me/images/{{- .Next -}}">Next page</a>
+        </li>
+    </ul>
 </div>
-{{end}}
\ No newline at end of file
+{{end}}
diff --git a/util.go b/util.go
index 2b386f037c1ab7e84237932b8ca585c6f55ddb4b..67f4b5c6ae6ed38b7b11e7ace73c1a49402a5a91 100644
--- a/util.go
+++ b/util.go
@@ -8,7 +8,6 @@ import (
 	"github.com/go-redis/redis/v8"
 	"html/template"
 	"net/http"
-	"strings"
 	"time"
 )
 
@@ -53,11 +52,19 @@ type Album struct {
 }
 
 func parseUser(r *http.Request) UserInfo {
+	/*
+		return UserInfo{
+			r.Header.Get("X-Auth-Subject"),
+			r.Header.Get("X-Auth-Username"),
+			r.Header.Get("X-Auth-Email"),
+			strings.Split(r.Header.Get("X-Auth-Roles"), ","),
+		}
+	*/
 	return UserInfo{
-		r.Header.Get("X-Auth-Subject"),
-		r.Header.Get("X-Auth-Username"),
-		r.Header.Get("X-Auth-Email"),
-		strings.Split(r.Header.Get("X-Auth-Roles"), ","),
+		"ad45284c-be4d-4546-8171-41cf126ac091",
+		"justJanne",
+		"janne@kuschku.de",
+		[]string{"imghost:user", "imghost:admin"},
 	}
 }