diff --git a/api/category.go b/api/category.go
index b8699e2b..63435176 100644
--- a/api/category.go
+++ b/api/category.go
@@ -16,29 +16,28 @@ import (
 func (c *Controller) CreateCategory(w http.ResponseWriter, r *http.Request) {
 	category, err := decodeCategoryPayload(r.Body)
 	if err != nil {
-		json.BadRequest(w, err)
+		json.BadRequest(w, r, err)
 		return
 	}
 
 	userID := request.UserID(r)
 	category.UserID = userID
 	if err := category.ValidateCategoryCreation(); err != nil {
-		json.BadRequest(w, err)
+		json.BadRequest(w, r, err)
 		return
 	}
 
 	if c, err := c.store.CategoryByTitle(userID, category.Title); err != nil || c != nil {
-		json.BadRequest(w, errors.New("This category already exists"))
+		json.BadRequest(w, r, errors.New("This category already exists"))
 		return
 	}
 
-	err = c.store.CreateCategory(category)
-	if err != nil {
-		json.ServerError(w, err)
+	if err := c.store.CreateCategory(category); err != nil {
+		json.ServerError(w, r, err)
 		return
 	}
 
-	json.Created(w, category)
+	json.Created(w, r, category)
 }
 
 // UpdateCategory is the API handler to update a category.
@@ -47,31 +46,31 @@ func (c *Controller) UpdateCategory(w http.ResponseWriter, r *http.Request) {
 
 	category, err := decodeCategoryPayload(r.Body)
 	if err != nil {
-		json.BadRequest(w, err)
+		json.BadRequest(w, r, err)
 		return
 	}
 
 	category.UserID = request.UserID(r)
 	category.ID = categoryID
 	if err := category.ValidateCategoryModification(); err != nil {
-		json.BadRequest(w, err)
+		json.BadRequest(w, r, err)
 		return
 	}
 
 	err = c.store.UpdateCategory(category)
 	if err != nil {
-		json.ServerError(w, err)
+		json.ServerError(w, r, err)
 		return
 	}
 
-	json.Created(w, category)
+	json.Created(w, r, category)
 }
 
 // GetCategories is the API handler to get a list of categories for a given user.
 func (c *Controller) GetCategories(w http.ResponseWriter, r *http.Request) {
 	categories, err := c.store.Categories(request.UserID(r))
 	if err != nil {
-		json.ServerError(w, err)
+		json.ServerError(w, r, err)
 		return
 	}
 
@@ -84,14 +83,14 @@ func (c *Controller) RemoveCategory(w http.ResponseWriter, r *http.Request) {
 	categoryID := request.RouteInt64Param(r, "categoryID")
 
 	if !c.store.CategoryExists(userID, categoryID) {
-		json.NotFound(w, errors.New("Category not found"))
+		json.NotFound(w, r)
 		return
 	}
 
 	if err := c.store.RemoveCategory(userID, categoryID); err != nil {
-		json.ServerError(w, err)
+		json.ServerError(w, r, err)
 		return
 	}
 
-	json.NoContent(w)
+	json.NoContent(w, r)
 }
diff --git a/api/entry.go b/api/entry.go
index a1ea87fe..c7277854 100644
--- a/api/entry.go
+++ b/api/entry.go
@@ -26,12 +26,12 @@ func (c *Controller) GetFeedEntry(w http.ResponseWriter, r *http.Request) {
 
 	entry, err := builder.GetEntry()
 	if err != nil {
-		json.ServerError(w, err)
+		json.ServerError(w, r, err)
 		return
 	}
 
 	if entry == nil {
-		json.NotFound(w, errors.New("Entry not found"))
+		json.NotFound(w, r)
 		return
 	}
 
@@ -46,12 +46,12 @@ func (c *Controller) GetEntry(w http.ResponseWriter, r *http.Request) {
 
 	entry, err := builder.GetEntry()
 	if err != nil {
-		json.ServerError(w, err)
+		json.ServerError(w, r, err)
 		return
 	}
 
 	if entry == nil {
-		json.NotFound(w, errors.New("Entry not found"))
+		json.NotFound(w, r)
 		return
 	}
 
@@ -65,27 +65,27 @@ func (c *Controller) GetFeedEntries(w http.ResponseWriter, r *http.Request) {
 	status := request.QueryStringParam(r, "status", "")
 	if status != "" {
 		if err := model.ValidateEntryStatus(status); err != nil {
-			json.BadRequest(w, err)
+			json.BadRequest(w, r, err)
 			return
 		}
 	}
 
 	order := request.QueryStringParam(r, "order", model.DefaultSortingOrder)
 	if err := model.ValidateEntryOrder(order); err != nil {
-		json.BadRequest(w, err)
+		json.BadRequest(w, r, err)
 		return
 	}
 
 	direction := request.QueryStringParam(r, "direction", model.DefaultSortingDirection)
 	if err := model.ValidateDirection(direction); err != nil {
-		json.BadRequest(w, err)
+		json.BadRequest(w, r, err)
 		return
 	}
 
 	limit := request.QueryIntParam(r, "limit", 100)
 	offset := request.QueryIntParam(r, "offset", 0)
 	if err := model.ValidateRange(offset, limit); err != nil {
-		json.BadRequest(w, err)
+		json.BadRequest(w, r, err)
 		return
 	}
 
@@ -100,13 +100,13 @@ func (c *Controller) GetFeedEntries(w http.ResponseWriter, r *http.Request) {
 
 	entries, err := builder.GetEntries()
 	if err != nil {
-		json.ServerError(w, err)
+		json.ServerError(w, r, err)
 		return
 	}
 
 	count, err := builder.CountEntries()
 	if err != nil {
-		json.ServerError(w, err)
+		json.ServerError(w, r, err)
 		return
 	}
 
@@ -118,27 +118,27 @@ func (c *Controller) GetEntries(w http.ResponseWriter, r *http.Request) {
 	status := request.QueryStringParam(r, "status", "")
 	if status != "" {
 		if err := model.ValidateEntryStatus(status); err != nil {
-			json.BadRequest(w, err)
+			json.BadRequest(w, r, err)
 			return
 		}
 	}
 
 	order := request.QueryStringParam(r, "order", model.DefaultSortingOrder)
 	if err := model.ValidateEntryOrder(order); err != nil {
-		json.BadRequest(w, err)
+		json.BadRequest(w, r, err)
 		return
 	}
 
 	direction := request.QueryStringParam(r, "direction", model.DefaultSortingDirection)
 	if err := model.ValidateDirection(direction); err != nil {
-		json.BadRequest(w, err)
+		json.BadRequest(w, r, err)
 		return
 	}
 
 	limit := request.QueryIntParam(r, "limit", 100)
 	offset := request.QueryIntParam(r, "offset", 0)
 	if err := model.ValidateRange(offset, limit); err != nil {
-		json.BadRequest(w, err)
+		json.BadRequest(w, r, err)
 		return
 	}
 
@@ -152,13 +152,13 @@ func (c *Controller) GetEntries(w http.ResponseWriter, r *http.Request) {
 
 	entries, err := builder.GetEntries()
 	if err != nil {
-		json.ServerError(w, err)
+		json.ServerError(w, r, err)
 		return
 	}
 
 	count, err := builder.CountEntries()
 	if err != nil {
-		json.ServerError(w, err)
+		json.ServerError(w, r, err)
 		return
 	}
 
@@ -169,32 +169,32 @@ func (c *Controller) GetEntries(w http.ResponseWriter, r *http.Request) {
 func (c *Controller) SetEntryStatus(w http.ResponseWriter, r *http.Request) {
 	entryIDs, status, err := decodeEntryStatusPayload(r.Body)
 	if err != nil {
-		json.BadRequest(w, errors.New("Invalid JSON payload"))
+		json.BadRequest(w , r, errors.New("Invalid JSON payload"))
 		return
 	}
 
 	if err := model.ValidateEntryStatus(status); err != nil {
-		json.BadRequest(w, err)
+		json.BadRequest(w, r, err)
 		return
 	}
 
 	if err := c.store.SetEntriesStatus(request.UserID(r), entryIDs, status); err != nil {
-		json.ServerError(w, err)
+		json.ServerError(w, r, err)
 		return
 	}
 
-	json.NoContent(w)
+	json.NoContent(w, r)
 }
 
 // ToggleBookmark is the API handler to toggle bookmark status.
 func (c *Controller) ToggleBookmark(w http.ResponseWriter, r *http.Request) {
 	entryID := request.RouteInt64Param(r, "entryID")
 	if err := c.store.ToggleBookmark(request.UserID(r), entryID); err != nil {
-		json.ServerError(w, err)
+		json.ServerError(w, r, err)
 		return
 	}
 
-	json.NoContent(w)
+	json.NoContent(w, r)
 }
 
 func configureFilters(builder *storage.EntryQueryBuilder, r *http.Request) {
diff --git a/api/feed.go b/api/feed.go
index d193d2e0..914ba0d7 100644
--- a/api/feed.go
+++ b/api/feed.go
@@ -16,29 +16,29 @@ import (
 func (c *Controller) CreateFeed(w http.ResponseWriter, r *http.Request) {
 	feedInfo, err := decodeFeedCreationPayload(r.Body)
 	if err != nil {
-		json.BadRequest(w, err)
+		json.BadRequest(w, r, err)
 		return
 	}
 
 	if feedInfo.FeedURL == "" {
-		json.BadRequest(w, errors.New("The feed_url is required"))
+		json.BadRequest(w, r, errors.New("The feed_url is required"))
 		return
 	}
 
 	if feedInfo.CategoryID <= 0 {
-		json.BadRequest(w, errors.New("The category_id is required"))
+		json.BadRequest(w, r, errors.New("The category_id is required"))
 		return
 	}
 
 	userID := request.UserID(r)
 
 	if c.store.FeedURLExists(userID, feedInfo.FeedURL) {
-		json.BadRequest(w, errors.New("This feed_url already exists"))
+		json.BadRequest(w, r, errors.New("This feed_url already exists"))
 		return
 	}
 
 	if !c.store.CategoryExists(userID, feedInfo.CategoryID) {
-		json.BadRequest(w, errors.New("This category_id doesn't exists or doesn't belongs to this user"))
+		json.BadRequest(w, r, errors.New("This category_id doesn't exists or doesn't belongs to this user"))
 		return
 	}
 
@@ -52,7 +52,7 @@ func (c *Controller) CreateFeed(w http.ResponseWriter, r *http.Request) {
 		feedInfo.Password,
 	)
 	if err != nil {
-		json.ServerError(w, err)
+		json.ServerError(w, r, err)
 		return
 	}
 
@@ -60,7 +60,7 @@ func (c *Controller) CreateFeed(w http.ResponseWriter, r *http.Request) {
 		FeedID int64 `json:"feed_id"`
 	}
 
-	json.Created(w, &result{FeedID: feed.ID})
+	json.Created(w, r, &result{FeedID: feed.ID})
 }
 
 // RefreshFeed is the API handler to refresh a feed.
@@ -69,17 +69,17 @@ func (c *Controller) RefreshFeed(w http.ResponseWriter, r *http.Request) {
 	userID := request.UserID(r)
 
 	if !c.store.FeedExists(userID, feedID) {
-		json.NotFound(w, errors.New("Unable to find this feed"))
+		json.NotFound(w, r)
 		return
 	}
 
 	err := c.feedHandler.RefreshFeed(userID, feedID)
 	if err != nil {
-		json.ServerError(w, err)
+		json.ServerError(w, r, err)
 		return
 	}
 
-	json.NoContent(w)
+	json.NoContent(w, r)
 }
 
 // UpdateFeed is the API handler that is used to update a feed.
@@ -87,7 +87,7 @@ func (c *Controller) UpdateFeed(w http.ResponseWriter, r *http.Request) {
 	feedID := request.RouteInt64Param(r, "feedID")
 	feedChanges, err := decodeFeedModificationPayload(r.Body)
 	if err != nil {
-		json.BadRequest(w, err)
+		json.BadRequest(w, r, err)
 		return
 	}
 
@@ -95,41 +95,41 @@ func (c *Controller) UpdateFeed(w http.ResponseWriter, r *http.Request) {
 
 	originalFeed, err := c.store.FeedByID(userID, feedID)
 	if err != nil {
-		json.NotFound(w, errors.New("Unable to find this feed"))
+		json.NotFound(w, r)
 		return
 	}
 
 	if originalFeed == nil {
-		json.NotFound(w, errors.New("Feed not found"))
+		json.NotFound(w, r)
 		return
 	}
 
 	feedChanges.Update(originalFeed)
 
 	if !c.store.CategoryExists(userID, originalFeed.Category.ID) {
-		json.BadRequest(w, errors.New("This category_id doesn't exists or doesn't belongs to this user"))
+		json.BadRequest(w, r, errors.New("This category_id doesn't exists or doesn't belongs to this user"))
 		return
 	}
 
 	if err := c.store.UpdateFeed(originalFeed); err != nil {
-		json.ServerError(w, err)
+		json.ServerError(w, r, err)
 		return
 	}
 
 	originalFeed, err = c.store.FeedByID(userID, feedID)
 	if err != nil {
-		json.ServerError(w, err)
+		json.ServerError(w, r, err)
 		return
 	}
 
-	json.Created(w, originalFeed)
+	json.Created(w, r, originalFeed)
 }
 
 // GetFeeds is the API handler that get all feeds that belongs to the given user.
 func (c *Controller) GetFeeds(w http.ResponseWriter, r *http.Request) {
 	feeds, err := c.store.Feeds(request.UserID(r))
 	if err != nil {
-		json.ServerError(w, err)
+		json.ServerError(w, r, err)
 		return
 	}
 
@@ -141,12 +141,12 @@ func (c *Controller) GetFeed(w http.ResponseWriter, r *http.Request) {
 	feedID := request.RouteInt64Param(r, "feedID")
 	feed, err := c.store.FeedByID(request.UserID(r), feedID)
 	if err != nil {
-		json.ServerError(w, err)
+		json.ServerError(w, r, err)
 		return
 	}
 
 	if feed == nil {
-		json.NotFound(w, errors.New("Feed not found"))
+		json.NotFound(w, r)
 		return
 	}
 
@@ -159,14 +159,14 @@ func (c *Controller) RemoveFeed(w http.ResponseWriter, r *http.Request) {
 	userID := request.UserID(r)
 
 	if !c.store.FeedExists(userID, feedID) {
-		json.NotFound(w, errors.New("Feed not found"))
+		json.NotFound(w, r)
 		return
 	}
 
 	if err := c.store.RemoveFeed(userID, feedID); err != nil {
-		json.ServerError(w, err)
+		json.ServerError(w, r, err)
 		return
 	}
 
-	json.NoContent(w)
+	json.NoContent(w, r)
 }
diff --git a/api/icon.go b/api/icon.go
index de01fadb..1954769a 100644
--- a/api/icon.go
+++ b/api/icon.go
@@ -5,7 +5,6 @@
 package api // import "miniflux.app/api"
 
 import (
-	"errors"
 	"net/http"
 
 	"miniflux.app/http/request"
@@ -17,18 +16,18 @@ func (c *Controller) FeedIcon(w http.ResponseWriter, r *http.Request) {
 	feedID := request.RouteInt64Param(r, "feedID")
 
 	if !c.store.HasIcon(feedID) {
-		json.NotFound(w, errors.New("This feed doesn't have any icon"))
+		json.NotFound(w, r)
 		return
 	}
 
 	icon, err := c.store.IconByFeedID(request.UserID(r), feedID)
 	if err != nil {
-		json.ServerError(w, err)
+		json.ServerError(w, r, err)
 		return
 	}
 
 	if icon == nil {
-		json.NotFound(w, errors.New("This feed doesn't have any icon"))
+		json.NotFound(w, r)
 		return
 	}
 
diff --git a/api/opml.go b/api/opml.go
index eb214f32..199c0e72 100644
--- a/api/opml.go
+++ b/api/opml.go
@@ -18,11 +18,11 @@ func (c *Controller) Export(w http.ResponseWriter, r *http.Request) {
 	opmlHandler := opml.NewHandler(c.store)
 	opml, err := opmlHandler.Export(request.UserID(r))
 	if err != nil {
-		json.ServerError(w, err)
+		json.ServerError(w, r, err)
 		return
 	}
 
-	xml.OK(w, opml)
+	xml.OK(w, r, opml)
 }
 
 // Import is the API handler that import an OPML file.
@@ -31,9 +31,9 @@ func (c *Controller) Import(w http.ResponseWriter, r *http.Request) {
 	err := opmlHandler.Import(request.UserID(r), r.Body)
 	defer r.Body.Close()
 	if err != nil {
-		json.ServerError(w, err)
+		json.ServerError(w, r, err)
 		return
 	}
 
-	json.Created(w, map[string]string{"message": "Feeds imported successfully"})
+	json.Created(w, r, map[string]string{"message": "Feeds imported successfully"})
 }
diff --git a/api/subscription.go b/api/subscription.go
index ff4c7cf8..701759d6 100644
--- a/api/subscription.go
+++ b/api/subscription.go
@@ -5,7 +5,6 @@
 package api // import "miniflux.app/api"
 
 import (
-	"fmt"
 	"net/http"
 
 	"miniflux.app/http/response/json"
@@ -16,7 +15,7 @@ import (
 func (c *Controller) GetSubscriptions(w http.ResponseWriter, r *http.Request) {
 	subscriptionInfo, err := decodeURLPayload(r.Body)
 	if err != nil {
-		json.BadRequest(w, err)
+		json.BadRequest(w, r, err)
 		return
 	}
 
@@ -27,12 +26,12 @@ func (c *Controller) GetSubscriptions(w http.ResponseWriter, r *http.Request) {
 		subscriptionInfo.Password,
 	)
 	if err != nil {
-		json.ServerError(w, err)
+		json.ServerError(w, r, err)
 		return
 	}
 
 	if subscriptions == nil {
-		json.NotFound(w, fmt.Errorf("No subscription found"))
+		json.NotFound(w, r)
 		return
 	}
 
diff --git a/api/user.go b/api/user.go
index b9274bbe..db1e0496 100644
--- a/api/user.go
+++ b/api/user.go
@@ -16,7 +16,7 @@ import (
 func (c *Controller) CurrentUser(w http.ResponseWriter, r *http.Request) {
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		json.ServerError(w, err)
+		json.ServerError(w, r, err)
 		return
 	}
 
@@ -26,85 +26,85 @@ func (c *Controller) CurrentUser(w http.ResponseWriter, r *http.Request) {
 // CreateUser is the API handler to create a new user.
 func (c *Controller) CreateUser(w http.ResponseWriter, r *http.Request) {
 	if !request.IsAdminUser(r) {
-		json.Forbidden(w)
+		json.Forbidden(w, r)
 		return
 	}
 
 	user, err := decodeUserCreationPayload(r.Body)
 	if err != nil {
-		json.BadRequest(w, err)
+		json.BadRequest(w, r, err)
 		return
 	}
 
 	if err := user.ValidateUserCreation(); err != nil {
-		json.BadRequest(w, err)
+		json.BadRequest(w, r, err)
 		return
 	}
 
 	if c.store.UserExists(user.Username) {
-		json.BadRequest(w, errors.New("This user already exists"))
+		json.BadRequest(w, r, errors.New("This user already exists"))
 		return
 	}
 
 	err = c.store.CreateUser(user)
 	if err != nil {
-		json.ServerError(w, err)
+		json.ServerError(w, r, err)
 		return
 	}
 
 	user.Password = ""
-	json.Created(w, user)
+	json.Created(w, r, user)
 }
 
 // UpdateUser is the API handler to update the given user.
 func (c *Controller) UpdateUser(w http.ResponseWriter, r *http.Request) {
 	if !request.IsAdminUser(r) {
-		json.Forbidden(w)
+		json.Forbidden(w, r)
 		return
 	}
 
 	userID := request.RouteInt64Param(r, "userID")
 	userChanges, err := decodeUserModificationPayload(r.Body)
 	if err != nil {
-		json.BadRequest(w, err)
+		json.BadRequest(w, r, err)
 		return
 	}
 
 	originalUser, err := c.store.UserByID(userID)
 	if err != nil {
-		json.BadRequest(w, errors.New("Unable to fetch this user from the database"))
+		json.BadRequest(w, r, errors.New("Unable to fetch this user from the database"))
 		return
 	}
 
 	if originalUser == nil {
-		json.NotFound(w, errors.New("User not found"))
+		json.NotFound(w, r)
 		return
 	}
 
 	userChanges.Update(originalUser)
 	if err := originalUser.ValidateUserModification(); err != nil {
-		json.BadRequest(w, err)
+		json.BadRequest(w, r, err)
 		return
 	}
 
 	if err = c.store.UpdateUser(originalUser); err != nil {
-		json.ServerError(w, err)
+		json.ServerError(w, r, err)
 		return
 	}
 
-	json.Created(w, originalUser)
+	json.Created(w, r, originalUser)
 }
 
 // Users is the API handler to get the list of users.
 func (c *Controller) Users(w http.ResponseWriter, r *http.Request) {
 	if !request.IsAdminUser(r) {
-		json.Forbidden(w)
+		json.Forbidden(w, r)
 		return
 	}
 
 	users, err := c.store.Users()
 	if err != nil {
-		json.ServerError(w, err)
+		json.ServerError(w, r, err)
 		return
 	}
 
@@ -115,19 +115,19 @@ func (c *Controller) Users(w http.ResponseWriter, r *http.Request) {
 // UserByID is the API handler to fetch the given user by the ID.
 func (c *Controller) UserByID(w http.ResponseWriter, r *http.Request) {
 	if !request.IsAdminUser(r) {
-		json.Forbidden(w)
+		json.Forbidden(w, r)
 		return
 	}
 
 	userID := request.RouteInt64Param(r, "userID")
 	user, err := c.store.UserByID(userID)
 	if err != nil {
-		json.BadRequest(w, errors.New("Unable to fetch this user from the database"))
+		json.BadRequest(w, r, errors.New("Unable to fetch this user from the database"))
 		return
 	}
 
 	if user == nil {
-		json.NotFound(w, errors.New("User not found"))
+		json.NotFound(w, r)
 		return
 	}
 
@@ -138,19 +138,19 @@ func (c *Controller) UserByID(w http.ResponseWriter, r *http.Request) {
 // UserByUsername is the API handler to fetch the given user by the username.
 func (c *Controller) UserByUsername(w http.ResponseWriter, r *http.Request) {
 	if !request.IsAdminUser(r) {
-		json.Forbidden(w)
+		json.Forbidden(w, r)
 		return
 	}
 
 	username := request.RouteStringParam(r, "username")
 	user, err := c.store.UserByUsername(username)
 	if err != nil {
-		json.BadRequest(w, errors.New("Unable to fetch this user from the database"))
+		json.BadRequest(w, r, errors.New("Unable to fetch this user from the database"))
 		return
 	}
 
 	if user == nil {
-		json.NotFound(w, errors.New("User not found"))
+		json.NotFound(w, r)
 		return
 	}
 
@@ -160,26 +160,26 @@ func (c *Controller) UserByUsername(w http.ResponseWriter, r *http.Request) {
 // RemoveUser is the API handler to remove an existing user.
 func (c *Controller) RemoveUser(w http.ResponseWriter, r *http.Request) {
 	if !request.IsAdminUser(r) {
-		json.Forbidden(w)
+		json.Forbidden(w, r)
 		return
 	}
 
 	userID := request.RouteInt64Param(r, "userID")
 	user, err := c.store.UserByID(userID)
 	if err != nil {
-		json.ServerError(w, err)
+		json.ServerError(w, r, err)
 		return
 	}
 
 	if user == nil {
-		json.NotFound(w, errors.New("User not found"))
+		json.NotFound(w, r)
 		return
 	}
 
 	if err := c.store.RemoveUser(user.ID); err != nil {
-		json.BadRequest(w, errors.New("Unable to remove this user from the database"))
+		json.BadRequest(w, r, errors.New("Unable to remove this user from the database"))
 		return
 	}
 
-	json.NoContent(w)
+	json.NoContent(w, r)
 }
diff --git a/daemon/routes.go b/daemon/routes.go
index 88b8c206..f89b0be8 100644
--- a/daemon/routes.go
+++ b/daemon/routes.go
@@ -35,7 +35,6 @@ func routes(cfg *config.Config, store *storage.Storage, feedHandler *feed.Handle
 	router.Use(middleware.ClientIP)
 	router.Use(middleware.HeaderConfig)
 	router.Use(middleware.Logging)
-	router.Use(middleware.CommonHeaders)
 
 	router.HandleFunc("/healthcheck", func(w http.ResponseWriter, r *http.Request) {
 		w.Write([]byte("OK"))
diff --git a/fever/fever.go b/fever/fever.go
index b7c8bc8d..cb658743 100644
--- a/fever/fever.go
+++ b/fever/fever.go
@@ -184,13 +184,13 @@ func (c *Controller) handleGroups(w http.ResponseWriter, r *http.Request) {
 
 	categories, err := c.store.Categories(userID)
 	if err != nil {
-		json.ServerError(w, err)
+		json.ServerError(w, r, err)
 		return
 	}
 
 	feeds, err := c.store.Feeds(userID)
 	if err != nil {
-		json.ServerError(w, err)
+		json.ServerError(w, r, err)
 		return
 	}
 
@@ -234,7 +234,7 @@ func (c *Controller) handleFeeds(w http.ResponseWriter, r *http.Request) {
 
 	feeds, err := c.store.Feeds(userID)
 	if err != nil {
-		json.ServerError(w, err)
+		json.ServerError(w, r, err)
 		return
 	}
 
@@ -287,7 +287,7 @@ func (c *Controller) handleFavicons(w http.ResponseWriter, r *http.Request) {
 
 	icons, err := c.store.Icons(userID)
 	if err != nil {
-		json.ServerError(w, err)
+		json.ServerError(w, r, err)
 		return
 	}
 
@@ -371,7 +371,7 @@ func (c *Controller) handleItems(w http.ResponseWriter, r *http.Request) {
 
 	entries, err := builder.GetEntries()
 	if err != nil {
-		json.ServerError(w, err)
+		json.ServerError(w, r, err)
 		return
 	}
 
@@ -379,7 +379,7 @@ func (c *Controller) handleItems(w http.ResponseWriter, r *http.Request) {
 	builder.WithoutStatus(model.EntryStatusRemoved)
 	result.Total, err = builder.CountEntries()
 	if err != nil {
-		json.ServerError(w, err)
+		json.ServerError(w, r, err)
 		return
 	}
 
@@ -427,7 +427,7 @@ func (c *Controller) handleUnreadItems(w http.ResponseWriter, r *http.Request) {
 	builder.WithStatus(model.EntryStatusUnread)
 	entries, err := builder.GetEntries()
 	if err != nil {
-		json.ServerError(w, err)
+		json.ServerError(w, r, err)
 		return
 	}
 
@@ -459,7 +459,7 @@ func (c *Controller) handleSavedItems(w http.ResponseWriter, r *http.Request) {
 
 	entryIDs, err := builder.GetEntryIDs()
 	if err != nil {
-		json.ServerError(w, err)
+		json.ServerError(w, r, err)
 		return
 	}
 
@@ -493,7 +493,7 @@ func (c *Controller) handleWriteItems(w http.ResponseWriter, r *http.Request) {
 
 	entry, err := builder.GetEntry()
 	if err != nil {
-		json.ServerError(w, err)
+		json.ServerError(w, r, err)
 		return
 	}
 
@@ -511,13 +511,13 @@ func (c *Controller) handleWriteItems(w http.ResponseWriter, r *http.Request) {
 	case "saved", "unsaved":
 		logger.Debug("[Fever] Mark entry #%d as saved/unsaved", entryID)
 		if err := c.store.ToggleBookmark(userID, entryID); err != nil {
-			json.ServerError(w, err)
+			json.ServerError(w, r, err)
 			return
 		}
 
 		settings, err := c.store.Integration(userID)
 		if err != nil {
-			json.ServerError(w, err)
+			json.ServerError(w, r, err)
 			return
 		}
 
diff --git a/http/response/builder.go b/http/response/builder.go
new file mode 100644
index 00000000..f17c62e4
--- /dev/null
+++ b/http/response/builder.go
@@ -0,0 +1,134 @@
+// Copyright 2018 Frédéric Guillot. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
+package response // import "miniflux.app/http/response"
+
+import (
+	"compress/flate"
+	"compress/gzip"
+	"fmt"
+	"net/http"
+	"strings"
+	"time"
+)
+
+const compressionThreshold = 1024
+
+// Builder generates HTTP responses.
+type Builder struct {
+	w                 http.ResponseWriter
+	r                 *http.Request
+	statusCode        int
+	headers           map[string]string
+	enableCompression bool
+	body              interface{}
+}
+
+// WithStatus uses the given status code to build the response.
+func (b *Builder) WithStatus(statusCode int) *Builder {
+	b.statusCode = statusCode
+	return b
+}
+
+// WithHeader adds the given HTTP header to the response.
+func (b *Builder) WithHeader(key, value string) *Builder {
+	b.headers[key] = value
+	return b
+}
+
+// WithBody uses the given body to build the response.
+func (b *Builder) WithBody(body interface{}) *Builder {
+	b.body = body
+	return b
+}
+
+// WithAttachment forces the document to be downloaded by the web browser.
+func (b *Builder) WithAttachment(filename string) *Builder {
+	b.headers["Content-Disposition"] = fmt.Sprintf("attachment; filename=%s", filename)
+	return b
+}
+
+// WithoutCompression disables HTTP compression.
+func (b *Builder) WithoutCompression() *Builder {
+	b.enableCompression = false
+	return b
+}
+
+// WithCaching adds caching headers to the response.
+func (b *Builder) WithCaching(etag string, duration time.Duration, callback func(*Builder)) {
+	b.headers["ETag"] = etag
+	b.headers["Cache-Control"] = "public"
+	b.headers["Expires"] = time.Now().Add(duration).Format(time.RFC1123)
+
+	if etag == b.r.Header.Get("If-None-Match") {
+		b.statusCode = http.StatusNotModified
+		b.body = nil
+		b.Write()
+	} else {
+		callback(b)
+	}
+}
+
+// Write generates the HTTP response.
+func (b *Builder) Write() {
+	if b.body == nil {
+		b.writeHeaders()
+		return
+	}
+
+	switch v := b.body.(type) {
+	case []byte:
+		b.compress(v)
+	case string:
+		b.compress([]byte(v))
+	case error:
+		b.compress([]byte(v.Error()))
+	}
+}
+
+func (b *Builder) writeHeaders() {
+	b.headers["X-XSS-Protection"] = "1; mode=block"
+	b.headers["X-Content-Type-Options"] = "nosniff"
+	b.headers["X-Frame-Options"] = "DENY"
+	b.headers["Content-Security-Policy"] = "default-src 'self'; img-src *; media-src *; frame-src *; child-src *"
+
+	for key, value := range b.headers {
+		b.w.Header().Set(key, value)
+	}
+
+	b.w.WriteHeader(b.statusCode)
+}
+
+func (b *Builder) compress(data []byte) {
+	if b.enableCompression && len(data) > compressionThreshold {
+		acceptEncoding := b.r.Header.Get("Accept-Encoding")
+
+		switch {
+		case strings.Contains(acceptEncoding, "gzip"):
+			b.headers["Content-Encoding"] = "gzip"
+			b.writeHeaders()
+
+			gzipWriter := gzip.NewWriter(b.w)
+			defer gzipWriter.Close()
+			gzipWriter.Write(data)
+			return
+		case strings.Contains(acceptEncoding, "deflate"):
+			b.headers["Content-Encoding"] = "deflate"
+			b.writeHeaders()
+
+			flateWriter, _ := flate.NewWriter(b.w, -1)
+			defer flateWriter.Close()
+			flateWriter.Write(data)
+			return
+		}
+	}
+
+	b.writeHeaders()
+	b.w.Write(data)
+}
+
+// New creates a new response builder.
+func New(w http.ResponseWriter, r *http.Request) *Builder {
+	return &Builder{w: w, r: r, statusCode: http.StatusOK, headers: make(map[string]string), enableCompression: true}
+}
diff --git a/http/response/builder_test.go b/http/response/builder_test.go
new file mode 100644
index 00000000..f4fef470
--- /dev/null
+++ b/http/response/builder_test.go
@@ -0,0 +1,351 @@
+// Copyright 2018 Frédéric Guillot. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
+package response // import "miniflux.app/http/response"
+
+import (
+	"errors"
+	"net/http"
+	"net/http/httptest"
+	"strings"
+	"testing"
+	"time"
+)
+
+func TestResponseHasCommonHeaders(t *testing.T) {
+	r, err := http.NewRequest("GET", "/", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	w := httptest.NewRecorder()
+
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		New(w, r).Write()
+	})
+
+	handler.ServeHTTP(w, r)
+	resp := w.Result()
+
+	headers := map[string]string{
+		"X-XSS-Protection":        "1; mode=block",
+		"X-Content-Type-Options":  "nosniff",
+		"X-Frame-Options":         "DENY",
+		"Content-Security-Policy": "default-src 'self'; img-src *; media-src *; frame-src *; child-src *",
+	}
+
+	for header, expected := range headers {
+		actual := resp.Header.Get(header)
+		if actual != expected {
+			t.Fatalf(`Unexpected header value, got %q instead of %q`, actual, expected)
+		}
+	}
+}
+
+func TestBuildResponseWithCustomStatusCode(t *testing.T) {
+	r, err := http.NewRequest("GET", "/", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	w := httptest.NewRecorder()
+
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		New(w, r).WithStatus(http.StatusNotAcceptable).Write()
+	})
+
+	handler.ServeHTTP(w, r)
+	resp := w.Result()
+
+	expectedStatusCode := http.StatusNotAcceptable
+	if resp.StatusCode != expectedStatusCode {
+		t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
+	}
+}
+
+func TestBuildResponseWithCustomHeader(t *testing.T) {
+	r, err := http.NewRequest("GET", "/", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	w := httptest.NewRecorder()
+
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		New(w, r).WithHeader("X-My-Header", "Value").Write()
+	})
+
+	handler.ServeHTTP(w, r)
+	resp := w.Result()
+
+	expected := "Value"
+	actual := resp.Header.Get("X-My-Header")
+	if actual != expected {
+		t.Fatalf(`Unexpected header value, got %q instead of %q`, actual, expected)
+	}
+}
+
+func TestBuildResponseWithAttachment(t *testing.T) {
+	r, err := http.NewRequest("GET", "/", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	w := httptest.NewRecorder()
+
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		New(w, r).WithAttachment("my_file.pdf").Write()
+	})
+
+	handler.ServeHTTP(w, r)
+	resp := w.Result()
+
+	expected := "attachment; filename=my_file.pdf"
+	actual := resp.Header.Get("Content-Disposition")
+	if actual != expected {
+		t.Fatalf(`Unexpected header value, got %q instead of %q`, actual, expected)
+	}
+}
+
+func TestBuildResponseWithError(t *testing.T) {
+	r, err := http.NewRequest("GET", "/", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	w := httptest.NewRecorder()
+
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		New(w, r).WithBody(errors.New("Some error")).Write()
+	})
+
+	handler.ServeHTTP(w, r)
+
+	expectedBody := `Some error`
+	actualBody := w.Body.String()
+	if actualBody != expectedBody {
+		t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, expectedBody)
+	}
+}
+
+func TestBuildResponseWithByteBody(t *testing.T) {
+	r, err := http.NewRequest("GET", "/", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	w := httptest.NewRecorder()
+
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		New(w, r).WithBody([]byte("body")).Write()
+	})
+
+	handler.ServeHTTP(w, r)
+
+	expectedBody := `body`
+	actualBody := w.Body.String()
+	if actualBody != expectedBody {
+		t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, expectedBody)
+	}
+}
+
+func TestBuildResponseWithCachingEnabled(t *testing.T) {
+	r, err := http.NewRequest("GET", "/", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	w := httptest.NewRecorder()
+
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		New(w, r).WithCaching("etag", 1*time.Minute, func(b *Builder) {
+			b.WithBody("cached body")
+			b.Write()
+		})
+	})
+
+	handler.ServeHTTP(w, r)
+	resp := w.Result()
+
+	expectedStatusCode := http.StatusOK
+	if resp.StatusCode != expectedStatusCode {
+		t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
+	}
+
+	expectedBody := `cached body`
+	actualBody := w.Body.String()
+	if actualBody != expectedBody {
+		t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, expectedBody)
+	}
+
+	expectedHeader := "public"
+	actualHeader := resp.Header.Get("Cache-Control")
+	if actualHeader != expectedHeader {
+		t.Fatalf(`Unexpected cache control header, got %q instead of %q`, actualHeader, expectedHeader)
+	}
+
+	if resp.Header.Get("Expires") == "" {
+		t.Fatalf(`Expires header should not be empty`)
+	}
+}
+
+func TestBuildResponseWithCachingAndEtag(t *testing.T) {
+	r, err := http.NewRequest("GET", "/", nil)
+	r.Header.Set("If-None-Match", "etag")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	w := httptest.NewRecorder()
+
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		New(w, r).WithCaching("etag", 1*time.Minute, func(b *Builder) {
+			b.WithBody("cached body")
+			b.Write()
+		})
+	})
+
+	handler.ServeHTTP(w, r)
+	resp := w.Result()
+
+	expectedStatusCode := http.StatusNotModified
+	if resp.StatusCode != expectedStatusCode {
+		t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
+	}
+
+	expectedBody := ``
+	actualBody := w.Body.String()
+	if actualBody != expectedBody {
+		t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, expectedBody)
+	}
+
+	expectedHeader := "public"
+	actualHeader := resp.Header.Get("Cache-Control")
+	if actualHeader != expectedHeader {
+		t.Fatalf(`Unexpected cache control header, got %q instead of %q`, actualHeader, expectedHeader)
+	}
+
+	if resp.Header.Get("Expires") == "" {
+		t.Fatalf(`Expires header should not be empty`)
+	}
+}
+
+func TestBuildResponseWithGzipCompression(t *testing.T) {
+	body := strings.Repeat("a", compressionThreshold+1)
+	r, err := http.NewRequest("GET", "/", nil)
+	r.Header.Set("Accept-Encoding", "gzip, deflate, br")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	w := httptest.NewRecorder()
+
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		New(w, r).WithBody(body).Write()
+	})
+
+	handler.ServeHTTP(w, r)
+	resp := w.Result()
+
+	expected := "gzip"
+	actual := resp.Header.Get("Content-Encoding")
+	if actual != expected {
+		t.Fatalf(`Unexpected header value, got %q instead of %q`, actual, expected)
+	}
+}
+
+func TestBuildResponseWithDeflateCompression(t *testing.T) {
+	body := strings.Repeat("a", compressionThreshold+1)
+	r, err := http.NewRequest("GET", "/", nil)
+	r.Header.Set("Accept-Encoding", "deflate")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	w := httptest.NewRecorder()
+
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		New(w, r).WithBody(body).Write()
+	})
+
+	handler.ServeHTTP(w, r)
+	resp := w.Result()
+
+	expected := "deflate"
+	actual := resp.Header.Get("Content-Encoding")
+	if actual != expected {
+		t.Fatalf(`Unexpected header value, got %q instead of %q`, actual, expected)
+	}
+}
+
+func TestBuildResponseWithCompressionDisabled(t *testing.T) {
+	body := strings.Repeat("a", compressionThreshold+1)
+	r, err := http.NewRequest("GET", "/", nil)
+	r.Header.Set("Accept-Encoding", "deflate")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	w := httptest.NewRecorder()
+
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		New(w, r).WithBody(body).WithoutCompression().Write()
+	})
+
+	handler.ServeHTTP(w, r)
+	resp := w.Result()
+
+	expected := ""
+	actual := resp.Header.Get("Content-Encoding")
+	if actual != expected {
+		t.Fatalf(`Unexpected header value, got %q instead of %q`, actual, expected)
+	}
+}
+
+func TestBuildResponseWithDeflateCompressionAndSmallPayload(t *testing.T) {
+	body := strings.Repeat("a", compressionThreshold)
+	r, err := http.NewRequest("GET", "/", nil)
+	r.Header.Set("Accept-Encoding", "deflate")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	w := httptest.NewRecorder()
+
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		New(w, r).WithBody(body).Write()
+	})
+
+	handler.ServeHTTP(w, r)
+	resp := w.Result()
+
+	expected := ""
+	actual := resp.Header.Get("Content-Encoding")
+	if actual != expected {
+		t.Fatalf(`Unexpected header value, got %q instead of %q`, actual, expected)
+	}
+}
+
+func TestBuildResponseWithoutCompressionHeader(t *testing.T) {
+	body := strings.Repeat("a", compressionThreshold+1)
+	r, err := http.NewRequest("GET", "/", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	w := httptest.NewRecorder()
+
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		New(w, r).WithBody(body).Write()
+	})
+
+	handler.ServeHTTP(w, r)
+	resp := w.Result()
+
+	expected := ""
+	actual := resp.Header.Get("Content-Encoding")
+	if actual != expected {
+		t.Fatalf(`Unexpected header value, got %q instead of %q`, actual, expected)
+	}
+}
diff --git a/http/response/doc.go b/http/response/doc.go
new file mode 100644
index 00000000..007e0fa4
--- /dev/null
+++ b/http/response/doc.go
@@ -0,0 +1,10 @@
+// Copyright 2018 Frédéric Guillot. All rights reserved.
+// Use of this source code is governed by the MIT license
+// that can be found in the LICENSE file.
+
+/*
+
+Package response contains everything related to HTTP responses.
+
+*/
+package response // import "miniflux.app/http/response"
diff --git a/http/response/html/doc.go b/http/response/html/doc.go
new file mode 100644
index 00000000..91d35439
--- /dev/null
+++ b/http/response/html/doc.go
@@ -0,0 +1,10 @@
+// Copyright 2018 Frédéric Guillot. All rights reserved.
+// Use of this source code is governed by the MIT license
+// that can be found in the LICENSE file.
+
+/*
+
+Package html contains HTML response functions.
+
+*/
+package html // import "miniflux.app/http/response/html"
diff --git a/http/response/html/html.go b/http/response/html/html.go
index 65a46490..f173fdbe 100644
--- a/http/response/html/html.go
+++ b/http/response/html/html.go
@@ -1,6 +1,6 @@
 // Copyright 2018 Frédéric Guillot. All rights reserved.
-// Use of this source code is governed by the Apache 2.0
-// license that can be found in the LICENSE file.
+// Use of this source code is governed by the MIT license
+// that can be found in the LICENSE file.
 
 package html // import "miniflux.app/http/response/html"
 
@@ -11,48 +11,64 @@ import (
 	"miniflux.app/logger"
 )
 
-// OK writes a standard HTML response.
-func OK(w http.ResponseWriter, r *http.Request, b []byte) {
-	w.Header().Set("Content-Type", "text/html; charset=utf-8")
-	response.Compress(w, r, b)
+// OK creates a new HTML response with a 200 status code.
+func OK(w http.ResponseWriter, r *http.Request, body interface{}) {
+	builder := response.New(w, r)
+	builder.WithHeader("Content-Type", "text/html; charset=utf-8")
+	builder.WithHeader("Cache-Control", "no-cache, max-age=0, must-revalidate, no-store")
+	builder.WithBody(body)
+	builder.Write()
 }
 
-// ServerError sends a 500 error to the browser.
-func ServerError(w http.ResponseWriter, err error) {
-	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
-	w.WriteHeader(http.StatusInternalServerError)
+// ServerError sends an internal error to the client.
+func ServerError(w http.ResponseWriter, r *http.Request, err error) {
+	logger.Error("[HTTP:Internal Server Error] %s => %v", r.URL, err)
 
-	if err != nil {
-		logger.Error("[Internal Server Error] %v", err)
-		w.Write([]byte("Internal Server Error: " + err.Error()))
-	} else {
-		w.Write([]byte("Internal Server Error"))
-	}
+	builder := response.New(w, r)
+	builder.WithStatus(http.StatusInternalServerError)
+	builder.WithHeader("Content-Type", "text/html; charset=utf-8")
+	builder.WithHeader("Cache-Control", "no-cache, max-age=0, must-revalidate, no-store")
+	builder.WithBody(err)
+	builder.Write()
 }
 
-// BadRequest sends a 400 error to the browser.
-func BadRequest(w http.ResponseWriter, err error) {
-	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
-	w.WriteHeader(http.StatusBadRequest)
+// BadRequest sends a bad request error to the client.
+func BadRequest(w http.ResponseWriter, r *http.Request, err error) {
+	logger.Error("[HTTP:Bad Request] %s => %v", r.URL, err)
 
-	if err != nil {
-		logger.Error("[Bad Request] %v", err)
-		w.Write([]byte("Bad Request: " + err.Error()))
-	} else {
-		w.Write([]byte("Bad Request"))
-	}
+	builder := response.New(w, r)
+	builder.WithStatus(http.StatusBadRequest)
+	builder.WithHeader("Content-Type", "text/html; charset=utf-8")
+	builder.WithHeader("Cache-Control", "no-cache, max-age=0, must-revalidate, no-store")
+	builder.WithBody(err)
+	builder.Write()
 }
 
-// NotFound sends a 404 error to the browser.
-func NotFound(w http.ResponseWriter) {
-	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
-	w.WriteHeader(http.StatusNotFound)
-	w.Write([]byte("Page Not Found"))
+// Forbidden sends a forbidden error to the client.
+func Forbidden(w http.ResponseWriter, r *http.Request) {
+	logger.Error("[HTTP:Forbidden] %s", r.URL)
+
+	builder := response.New(w, r)
+	builder.WithStatus(http.StatusForbidden)
+	builder.WithHeader("Content-Type", "text/html; charset=utf-8")
+	builder.WithHeader("Cache-Control", "no-cache, max-age=0, must-revalidate, no-store")
+	builder.WithBody("Access Forbidden")
+	builder.Write()
 }
 
-// Forbidden sends a 403 error to the browser.
-func Forbidden(w http.ResponseWriter) {
-	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
-	w.WriteHeader(http.StatusForbidden)
-	w.Write([]byte("Access Forbidden"))
+// NotFound sends a page not found error to the client.
+func NotFound(w http.ResponseWriter, r *http.Request) {
+	logger.Error("[HTTP:Not Found] %s", r.URL)
+
+	builder := response.New(w, r)
+	builder.WithStatus(http.StatusNotFound)
+	builder.WithHeader("Content-Type", "text/html; charset=utf-8")
+	builder.WithHeader("Cache-Control", "no-cache, max-age=0, must-revalidate, no-store")
+	builder.WithBody("Page Not Found")
+	builder.Write()
+}
+
+// Redirect redirects the user to another location.
+func Redirect(w http.ResponseWriter, r *http.Request, uri string) {
+	http.Redirect(w, r, uri, http.StatusFound)
 }
diff --git a/http/response/html/html_test.go b/http/response/html/html_test.go
new file mode 100644
index 00000000..91c2b744
--- /dev/null
+++ b/http/response/html/html_test.go
@@ -0,0 +1,212 @@
+// Copyright 2018 Frédéric Guillot. All rights reserved.
+// Use of this source code is governed by the MIT license
+// that can be found in the LICENSE file.
+
+package html // import "miniflux.app/http/response/html"
+
+import (
+	"errors"
+	"net/http"
+	"net/http/httptest"
+	"testing"
+)
+
+func TestOKResponse(t *testing.T) {
+	r, err := http.NewRequest("GET", "/", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	w := httptest.NewRecorder()
+
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		OK(w, r, "Some HTML")
+	})
+
+	handler.ServeHTTP(w, r)
+	resp := w.Result()
+
+	expectedStatusCode := http.StatusOK
+	if resp.StatusCode != expectedStatusCode {
+		t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
+	}
+
+	expectedBody := `Some HTML`
+	actualBody := w.Body.String()
+	if actualBody != expectedBody {
+		t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, expectedBody)
+	}
+
+	headers := map[string]string{
+		"Content-Type":  "text/html; charset=utf-8",
+		"Cache-Control": "no-cache, max-age=0, must-revalidate, no-store",
+	}
+
+	for header, expected := range headers {
+		actual := resp.Header.Get(header)
+		if actual != expected {
+			t.Fatalf(`Unexpected header value, got %q instead of %q`, actual, expected)
+		}
+	}
+}
+
+func TestServerErrorResponse(t *testing.T) {
+	r, err := http.NewRequest("GET", "/", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	w := httptest.NewRecorder()
+
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		ServerError(w, r, errors.New("Some error"))
+	})
+
+	handler.ServeHTTP(w, r)
+	resp := w.Result()
+
+	expectedStatusCode := http.StatusInternalServerError
+	if resp.StatusCode != expectedStatusCode {
+		t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
+	}
+
+	expectedBody := `Some error`
+	actualBody := w.Body.String()
+	if actualBody != expectedBody {
+		t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, expectedBody)
+	}
+
+	expectedContentType := "text/html; charset=utf-8"
+	actualContentType := resp.Header.Get("Content-Type")
+	if actualContentType != expectedContentType {
+		t.Fatalf(`Unexpected content type, got %q instead of %q`, actualContentType, expectedContentType)
+	}
+}
+
+func TestBadRequestResponse(t *testing.T) {
+	r, err := http.NewRequest("GET", "/", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	w := httptest.NewRecorder()
+
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		BadRequest(w, r, errors.New("Some error"))
+	})
+
+	handler.ServeHTTP(w, r)
+	resp := w.Result()
+
+	expectedStatusCode := http.StatusBadRequest
+	if resp.StatusCode != expectedStatusCode {
+		t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
+	}
+
+	expectedBody := `Some error`
+	actualBody := w.Body.String()
+	if actualBody != expectedBody {
+		t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, expectedBody)
+	}
+
+	expectedContentType := "text/html; charset=utf-8"
+	actualContentType := resp.Header.Get("Content-Type")
+	if actualContentType != expectedContentType {
+		t.Fatalf(`Unexpected content type, got %q instead of %q`, actualContentType, expectedContentType)
+	}
+}
+
+func TestForbiddenResponse(t *testing.T) {
+	r, err := http.NewRequest("GET", "/", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	w := httptest.NewRecorder()
+
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		Forbidden(w, r)
+	})
+
+	handler.ServeHTTP(w, r)
+	resp := w.Result()
+
+	expectedStatusCode := http.StatusForbidden
+	if resp.StatusCode != expectedStatusCode {
+		t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
+	}
+
+	expectedBody := `Access Forbidden`
+	actualBody := w.Body.String()
+	if actualBody != expectedBody {
+		t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, expectedBody)
+	}
+
+	expectedContentType := "text/html; charset=utf-8"
+	actualContentType := resp.Header.Get("Content-Type")
+	if actualContentType != expectedContentType {
+		t.Fatalf(`Unexpected content type, got %q instead of %q`, actualContentType, expectedContentType)
+	}
+}
+
+func TestNotFoundResponse(t *testing.T) {
+	r, err := http.NewRequest("GET", "/", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	w := httptest.NewRecorder()
+
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		NotFound(w, r)
+	})
+
+	handler.ServeHTTP(w, r)
+	resp := w.Result()
+
+	expectedStatusCode := http.StatusNotFound
+	if resp.StatusCode != expectedStatusCode {
+		t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
+	}
+
+	expectedBody := `Page Not Found`
+	actualBody := w.Body.String()
+	if actualBody != expectedBody {
+		t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, expectedBody)
+	}
+
+	expectedContentType := "text/html; charset=utf-8"
+	actualContentType := resp.Header.Get("Content-Type")
+	if actualContentType != expectedContentType {
+		t.Fatalf(`Unexpected content type, got %q instead of %q`, actualContentType, expectedContentType)
+	}
+}
+
+func TestRedirectResponse(t *testing.T) {
+	r, err := http.NewRequest("GET", "/", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	w := httptest.NewRecorder()
+
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		Redirect(w, r, "/path")
+	})
+
+	handler.ServeHTTP(w, r)
+
+	resp := w.Result()
+	defer resp.Body.Close()
+
+	expectedStatusCode := http.StatusFound
+	if resp.StatusCode != expectedStatusCode {
+		t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
+	}
+
+	expectedResult := "/path"
+	actualResult := resp.Header.Get("Location")
+	if actualResult != expectedResult {
+		t.Fatalf(`Unexpected redirect location, got %q instead of %q`, actualResult, expectedResult)
+	}
+}
diff --git a/http/response/json/doc.go b/http/response/json/doc.go
new file mode 100644
index 00000000..c2a74c1e
--- /dev/null
+++ b/http/response/json/doc.go
@@ -0,0 +1,10 @@
+// Copyright 2018 Frédéric Guillot. All rights reserved.
+// Use of this source code is governed by the MIT license
+// that can be found in the LICENSE file.
+
+/*
+
+Package json contains JSON response functions.
+
+*/
+package json // import "miniflux.app/http/response/json"
diff --git a/http/response/json/json.go b/http/response/json/json.go
index f19efb0b..680a20d2 100644
--- a/http/response/json/json.go
+++ b/http/response/json/json.go
@@ -1,6 +1,6 @@
 // Copyright 2018 Frédéric Guillot. All rights reserved.
-// Use of this source code is governed by the Apache 2.0
-// license that can be found in the LICENSE file.
+// Use of this source code is governed by the MIT license
+// that can be found in the LICENSE file.
 
 package json // import "miniflux.app/http/response/json"
 
@@ -13,93 +13,98 @@ import (
 	"miniflux.app/logger"
 )
 
-// OK sends a JSON response with the status code 200.
-func OK(w http.ResponseWriter, r *http.Request, v interface{}) {
-	commonHeaders(w)
-	response.Compress(w, r, toJSON(v))
+// OK creates a new JSON response with a 200 status code.
+func OK(w http.ResponseWriter, r *http.Request, body interface{}) {
+	builder := response.New(w, r)
+	builder.WithHeader("Content-Type", "application/json; charset=utf-8")
+	builder.WithBody(toJSON(body))
+	builder.Write()
 }
 
-// Created sends a JSON response with the status code 201.
-func Created(w http.ResponseWriter, v interface{}) {
-	commonHeaders(w)
-	w.WriteHeader(http.StatusCreated)
-	w.Write(toJSON(v))
+// Created sends a created response to the client.
+func Created(w http.ResponseWriter, r *http.Request, body interface{}) {
+	builder := response.New(w, r)
+	builder.WithStatus(http.StatusCreated)
+	builder.WithHeader("Content-Type", "application/json; charset=utf-8")
+	builder.WithBody(toJSON(body))
+	builder.Write()
 }
 
-// NoContent sends a JSON response with the status code 204.
-func NoContent(w http.ResponseWriter) {
-	commonHeaders(w)
-	w.WriteHeader(http.StatusNoContent)
+// NoContent sends a no content response to the client.
+func NoContent(w http.ResponseWriter, r *http.Request) {
+	builder := response.New(w, r)
+	builder.WithStatus(http.StatusNoContent)
+	builder.WithHeader("Content-Type", "application/json; charset=utf-8")
+	builder.Write()
 }
 
-// NotFound sends a JSON response with the status code 404.
-func NotFound(w http.ResponseWriter, err error) {
-	logger.Error("[Not Found] %v", err)
-	commonHeaders(w)
-	w.WriteHeader(http.StatusNotFound)
-	w.Write(encodeError(err))
+// ServerError sends an internal error to the client.
+func ServerError(w http.ResponseWriter, r *http.Request, err error) {
+	logger.Error("[HTTP:Internal Server Error] %s => %v", r.URL, err)
+
+	builder := response.New(w, r)
+	builder.WithStatus(http.StatusInternalServerError)
+	builder.WithHeader("Content-Type", "application/json; charset=utf-8")
+	builder.WithBody(toJSONError(err))
+	builder.Write()
 }
 
-// ServerError sends a JSON response with the status code 500.
-func ServerError(w http.ResponseWriter, err error) {
-	logger.Error("[Internal Server Error] %v", err)
-	commonHeaders(w)
-	w.WriteHeader(http.StatusInternalServerError)
+// BadRequest sends a bad request error to the client.
+func BadRequest(w http.ResponseWriter, r *http.Request, err error) {
+	logger.Error("[HTTP:Bad Request] %s => %v", r.URL, err)
 
-	if err != nil {
-		w.Write(encodeError(err))
-	}
+	builder := response.New(w, r)
+	builder.WithStatus(http.StatusBadRequest)
+	builder.WithHeader("Content-Type", "application/json; charset=utf-8")
+	builder.WithBody(toJSONError(err))
+	builder.Write()
 }
 
-// Forbidden sends a JSON response with the status code 403.
-func Forbidden(w http.ResponseWriter) {
-	logger.Info("[Forbidden]")
-	commonHeaders(w)
-	w.WriteHeader(http.StatusForbidden)
-	w.Write(encodeError(errors.New("Access Forbidden")))
+// Unauthorized sends a not authorized error to the client.
+func Unauthorized(w http.ResponseWriter, r *http.Request) {
+	logger.Error("[HTTP:Unauthorized] %s", r.URL)
+
+	builder := response.New(w, r)
+	builder.WithStatus(http.StatusUnauthorized)
+	builder.WithHeader("Content-Type", "application/json; charset=utf-8")
+	builder.WithBody(toJSONError(errors.New("Access Unauthorized")))
+	builder.Write()
 }
 
-// Unauthorized sends a JSON response with the status code 401.
-func Unauthorized(w http.ResponseWriter) {
-	commonHeaders(w)
-	w.WriteHeader(http.StatusUnauthorized)
-	w.Write(encodeError(errors.New("Access Unauthorized")))
+// Forbidden sends a forbidden error to the client.
+func Forbidden(w http.ResponseWriter, r *http.Request) {
+	logger.Error("[HTTP:Forbidden] %s", r.URL)
+
+	builder := response.New(w, r)
+	builder.WithStatus(http.StatusForbidden)
+	builder.WithHeader("Content-Type", "application/json; charset=utf-8")
+	builder.WithBody(toJSONError(errors.New("Access Forbidden")))
+	builder.Write()
 }
 
-// BadRequest sends a JSON response with the status code 400.
-func BadRequest(w http.ResponseWriter, err error) {
-	logger.Error("[Bad Request] %v", err)
-	commonHeaders(w)
-	w.WriteHeader(http.StatusBadRequest)
+// NotFound sends a page not found error to the client.
+func NotFound(w http.ResponseWriter, r *http.Request) {
+	logger.Error("[HTTP:Not Found] %s", r.URL)
 
-	if err != nil {
-		w.Write(encodeError(err))
-	}
+	builder := response.New(w, r)
+	builder.WithStatus(http.StatusNotFound)
+	builder.WithHeader("Content-Type", "application/json; charset=utf-8")
+	builder.WithBody(toJSONError(errors.New("Resource Not Found")))
+	builder.Write()
 }
 
-func commonHeaders(w http.ResponseWriter) {
-	w.Header().Set("Accept", "application/json")
-	w.Header().Set("Content-Type", "application/json; charset=utf-8")
-}
-
-func encodeError(err error) []byte {
+func toJSONError(err error) []byte {
 	type errorMsg struct {
 		ErrorMessage string `json:"error_message"`
 	}
 
-	tmp := errorMsg{ErrorMessage: err.Error()}
-	data, err := json.Marshal(tmp)
-	if err != nil {
-		logger.Error("json encoding error: %v", err)
-	}
-
-	return data
+	return toJSON(errorMsg{ErrorMessage: err.Error()})
 }
 
 func toJSON(v interface{}) []byte {
 	b, err := json.Marshal(v)
 	if err != nil {
-		logger.Error("json encoding error: %v", err)
+		logger.Error("[HTTP:JSON] %v", err)
 		return []byte("")
 	}
 
diff --git a/http/response/json/json_test.go b/http/response/json/json_test.go
new file mode 100644
index 00000000..d22e4687
--- /dev/null
+++ b/http/response/json/json_test.go
@@ -0,0 +1,313 @@
+// Copyright 2018 Frédéric Guillot. All rights reserved.
+// Use of this source code is governed by the MIT license
+// that can be found in the LICENSE file.
+
+package json // import "miniflux.app/http/response/json"
+
+import (
+	"errors"
+	"net/http"
+	"net/http/httptest"
+	"testing"
+)
+
+func TestOKResponse(t *testing.T) {
+	r, err := http.NewRequest("GET", "/", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	w := httptest.NewRecorder()
+
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		OK(w, r, map[string]string{"key": "value"})
+	})
+
+	handler.ServeHTTP(w, r)
+
+	resp := w.Result()
+	defer resp.Body.Close()
+
+	expectedStatusCode := http.StatusOK
+	if resp.StatusCode != expectedStatusCode {
+		t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
+	}
+
+	expectedBody := `{"key":"value"}`
+	actualBody := w.Body.String()
+	if actualBody != expectedBody {
+		t.Fatalf(`Unexpected body, got %q instead of %q`, actualBody, expectedBody)
+	}
+
+	expectedContentType := "application/json; charset=utf-8"
+	actualContentType := resp.Header.Get("Content-Type")
+	if actualContentType != expectedContentType {
+		t.Fatalf(`Unexpected content type, got %q instead of %q`, actualContentType, expectedContentType)
+	}
+}
+
+func TestCreatedResponse(t *testing.T) {
+	r, err := http.NewRequest("GET", "/", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	w := httptest.NewRecorder()
+
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		Created(w, r, map[string]string{"key": "value"})
+	})
+
+	handler.ServeHTTP(w, r)
+	resp := w.Result()
+
+	expectedStatusCode := http.StatusCreated
+	if resp.StatusCode != expectedStatusCode {
+		t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
+	}
+
+	expectedBody := `{"key":"value"}`
+	actualBody := w.Body.String()
+	if actualBody != expectedBody {
+		t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, expectedBody)
+	}
+
+	expectedContentType := "application/json; charset=utf-8"
+	actualContentType := resp.Header.Get("Content-Type")
+	if actualContentType != expectedContentType {
+		t.Fatalf(`Unexpected content type, got %q instead of %q`, actualContentType, expectedContentType)
+	}
+}
+
+func TestNoContentResponse(t *testing.T) {
+	r, err := http.NewRequest("GET", "/", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	w := httptest.NewRecorder()
+
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		NoContent(w, r)
+	})
+
+	handler.ServeHTTP(w, r)
+	resp := w.Result()
+
+	expectedStatusCode := http.StatusNoContent
+	if resp.StatusCode != expectedStatusCode {
+		t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
+	}
+
+	expectedBody := ``
+	actualBody := w.Body.String()
+	if actualBody != expectedBody {
+		t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, expectedBody)
+	}
+
+	expectedContentType := "application/json; charset=utf-8"
+	actualContentType := resp.Header.Get("Content-Type")
+	if actualContentType != expectedContentType {
+		t.Fatalf(`Unexpected content type, got %q instead of %q`, actualContentType, expectedContentType)
+	}
+}
+
+func TestServerErrorResponse(t *testing.T) {
+	r, err := http.NewRequest("GET", "/", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	w := httptest.NewRecorder()
+
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		ServerError(w, r, errors.New("some error"))
+	})
+
+	handler.ServeHTTP(w, r)
+
+	resp := w.Result()
+	defer resp.Body.Close()
+
+	expectedStatusCode := http.StatusInternalServerError
+	if resp.StatusCode != expectedStatusCode {
+		t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
+	}
+
+	expectedBody := `{"error_message":"some error"}`
+	actualBody := w.Body.String()
+	if actualBody != expectedBody {
+		t.Fatalf(`Unexpected body, got %q instead of %q`, actualBody, expectedBody)
+	}
+
+	expectedContentType := "application/json; charset=utf-8"
+	actualContentType := resp.Header.Get("Content-Type")
+	if actualContentType != expectedContentType {
+		t.Fatalf(`Unexpected content type, got %q instead of %q`, actualContentType, expectedContentType)
+	}
+}
+
+func TestBadRequestResponse(t *testing.T) {
+	r, err := http.NewRequest("GET", "/", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	w := httptest.NewRecorder()
+
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		BadRequest(w, r, errors.New("Some Error"))
+	})
+
+	handler.ServeHTTP(w, r)
+	resp := w.Result()
+
+	expectedStatusCode := http.StatusBadRequest
+	if resp.StatusCode != expectedStatusCode {
+		t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
+	}
+
+	expectedBody := `{"error_message":"Some Error"}`
+	actualBody := w.Body.String()
+	if actualBody != expectedBody {
+		t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, expectedBody)
+	}
+
+	expectedContentType := "application/json; charset=utf-8"
+	actualContentType := resp.Header.Get("Content-Type")
+	if actualContentType != expectedContentType {
+		t.Fatalf(`Unexpected content type, got %q instead of %q`, actualContentType, expectedContentType)
+	}
+}
+
+func TestUnauthorizedResponse(t *testing.T) {
+	r, err := http.NewRequest("GET", "/", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	w := httptest.NewRecorder()
+
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		Unauthorized(w, r)
+	})
+
+	handler.ServeHTTP(w, r)
+	resp := w.Result()
+
+	expectedStatusCode := http.StatusUnauthorized
+	if resp.StatusCode != expectedStatusCode {
+		t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
+	}
+
+	expectedBody := `{"error_message":"Access Unauthorized"}`
+	actualBody := w.Body.String()
+	if actualBody != expectedBody {
+		t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, expectedBody)
+	}
+
+	expectedContentType := "application/json; charset=utf-8"
+	actualContentType := resp.Header.Get("Content-Type")
+	if actualContentType != expectedContentType {
+		t.Fatalf(`Unexpected content type, got %q instead of %q`, actualContentType, expectedContentType)
+	}
+}
+
+func TestForbiddenResponse(t *testing.T) {
+	r, err := http.NewRequest("GET", "/", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	w := httptest.NewRecorder()
+
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		Forbidden(w, r)
+	})
+
+	handler.ServeHTTP(w, r)
+	resp := w.Result()
+
+	expectedStatusCode := http.StatusForbidden
+	if resp.StatusCode != expectedStatusCode {
+		t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
+	}
+
+	expectedBody := `{"error_message":"Access Forbidden"}`
+	actualBody := w.Body.String()
+	if actualBody != expectedBody {
+		t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, expectedBody)
+	}
+
+	expectedContentType := "application/json; charset=utf-8"
+	actualContentType := resp.Header.Get("Content-Type")
+	if actualContentType != expectedContentType {
+		t.Fatalf(`Unexpected content type, got %q instead of %q`, actualContentType, expectedContentType)
+	}
+}
+
+func TestNotFoundResponse(t *testing.T) {
+	r, err := http.NewRequest("GET", "/", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	w := httptest.NewRecorder()
+
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		NotFound(w, r)
+	})
+
+	handler.ServeHTTP(w, r)
+	resp := w.Result()
+
+	expectedStatusCode := http.StatusNotFound
+	if resp.StatusCode != expectedStatusCode {
+		t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
+	}
+
+	expectedBody := `{"error_message":"Resource Not Found"}`
+	actualBody := w.Body.String()
+	if actualBody != expectedBody {
+		t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, expectedBody)
+	}
+
+	expectedContentType := "application/json; charset=utf-8"
+	actualContentType := resp.Header.Get("Content-Type")
+	if actualContentType != expectedContentType {
+		t.Fatalf(`Unexpected content type, got %q instead of %q`, actualContentType, expectedContentType)
+	}
+}
+
+func TestBuildInvalidJSONResponse(t *testing.T) {
+	r, err := http.NewRequest("GET", "/", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	w := httptest.NewRecorder()
+
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		OK(w, r, make(chan int))
+	})
+
+	handler.ServeHTTP(w, r)
+	resp := w.Result()
+
+	expectedStatusCode := http.StatusOK
+	if resp.StatusCode != expectedStatusCode {
+		t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
+	}
+
+	expectedBody := ``
+	actualBody := w.Body.String()
+	if actualBody != expectedBody {
+		t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, expectedBody)
+	}
+
+	expectedContentType := "application/json; charset=utf-8"
+	actualContentType := resp.Header.Get("Content-Type")
+	if actualContentType != expectedContentType {
+		t.Fatalf(`Unexpected content type, got %q instead of %q`, actualContentType, expectedContentType)
+	}
+}
diff --git a/http/response/response.go b/http/response/response.go
deleted file mode 100644
index 4d731719..00000000
--- a/http/response/response.go
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2018 Frédéric Guillot. All rights reserved.
-// Use of this source code is governed by the Apache 2.0
-// license that can be found in the LICENSE file.
-
-package response // import "miniflux.app/http/response"
-
-import (
-	"compress/flate"
-	"compress/gzip"
-	"net/http"
-	"strings"
-	"time"
-)
-
-// Redirect redirects the user to another location.
-func Redirect(w http.ResponseWriter, r *http.Request, path string) {
-	http.Redirect(w, r, path, http.StatusFound)
-}
-
-// NotModified sends a response with a 304 status code.
-func NotModified(w http.ResponseWriter) {
-	w.WriteHeader(http.StatusNotModified)
-}
-
-// Cache returns a response with caching headers.
-func Cache(w http.ResponseWriter, r *http.Request, mimeType, etag string, data []byte, duration time.Duration) {
-	w.Header().Set("Content-Type", mimeType)
-	w.Header().Set("ETag", etag)
-	w.Header().Set("Cache-Control", "public")
-	w.Header().Set("Expires", time.Now().Add(duration).Format(time.RFC1123))
-
-	if etag == r.Header.Get("If-None-Match") {
-		w.WriteHeader(http.StatusNotModified)
-		return
-	}
-
-	switch mimeType {
-	case "text/javascript; charset=utf-8", "text/css; charset=utf-8":
-		Compress(w, r, data)
-	default:
-		w.Write(data)
-	}
-}
-
-// Compress the response sent to the browser.
-func Compress(w http.ResponseWriter, r *http.Request, data []byte) {
-	acceptEncoding := r.Header.Get("Accept-Encoding")
-
-	switch {
-	case strings.Contains(acceptEncoding, "gzip"):
-		w.Header().Set("Content-Encoding", "gzip")
-		gzipWriter := gzip.NewWriter(w)
-		defer gzipWriter.Close()
-		gzipWriter.Write(data)
-	case strings.Contains(acceptEncoding, "deflate"):
-		w.Header().Set("Content-Encoding", "deflate")
-		flateWriter, _ := flate.NewWriter(w, -1)
-		defer flateWriter.Close()
-		flateWriter.Write(data)
-	default:
-		w.Write(data)
-	}
-}
diff --git a/http/response/xml/doc.go b/http/response/xml/doc.go
new file mode 100644
index 00000000..908d2f96
--- /dev/null
+++ b/http/response/xml/doc.go
@@ -0,0 +1,10 @@
+// Copyright 2018 Frédéric Guillot. All rights reserved.
+// Use of this source code is governed by the MIT license
+// that can be found in the LICENSE file.
+
+/*
+
+Package xml contains XML response functions.
+
+*/
+package xml // import "miniflux.app/http/response/xml"
diff --git a/http/response/xml/xml.go b/http/response/xml/xml.go
index bceb5f16..771a85ef 100644
--- a/http/response/xml/xml.go
+++ b/http/response/xml/xml.go
@@ -5,19 +5,24 @@
 package xml // import "miniflux.app/http/response/xml"
 
 import (
-	"fmt"
 	"net/http"
+
+	"miniflux.app/http/response"
 )
 
-// OK sends a XML document.
-func OK(w http.ResponseWriter, data string) {
-	w.Header().Set("Content-Type", "text/xml")
-	w.Write([]byte(data))
+// OK writes a standard XML response with a status 200 OK.
+func OK(w http.ResponseWriter, r *http.Request, body interface{}) {
+	builder := response.New(w, r)
+	builder.WithHeader("Content-Type", "text/xml; charset=utf-8")
+	builder.WithBody(body)
+	builder.Write()
 }
 
-// Attachment forces the download of a XML document.
-func Attachment(w http.ResponseWriter, filename, data string) {
-	w.Header().Set("Content-Type", "text/xml")
-	w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename))
-	w.Write([]byte(data))
+// Attachment forces the XML document to be downloaded by the web browser.
+func Attachment(w http.ResponseWriter, r *http.Request, filename string, body interface{}) {
+	builder := response.New(w, r)
+	builder.WithHeader("Content-Type", "text/xml; charset=utf-8")
+	builder.WithAttachment(filename)
+	builder.WithBody(body)
+	builder.Write()
 }
diff --git a/http/response/xml/xml_test.go b/http/response/xml/xml_test.go
new file mode 100644
index 00000000..ada6bd4f
--- /dev/null
+++ b/http/response/xml/xml_test.go
@@ -0,0 +1,83 @@
+// Copyright 2018 Frédéric Guillot. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
+package xml // import "miniflux.app/http/response/xml"
+
+import (
+	"net/http"
+	"net/http/httptest"
+	"testing"
+)
+
+func TestOKResponse(t *testing.T) {
+	r, err := http.NewRequest("GET", "/", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	w := httptest.NewRecorder()
+
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		OK(w, r, "Some XML")
+	})
+
+	handler.ServeHTTP(w, r)
+	resp := w.Result()
+
+	expectedStatusCode := http.StatusOK
+	if resp.StatusCode != expectedStatusCode {
+		t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
+	}
+
+	expectedBody := `Some XML`
+	actualBody := w.Body.String()
+	if actualBody != expectedBody {
+		t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, expectedBody)
+	}
+
+	expectedContentType := "text/xml; charset=utf-8"
+	actualContentType := resp.Header.Get("Content-Type")
+	if actualContentType != expectedContentType {
+		t.Fatalf(`Unexpected content type, got %q instead of %q`, actualContentType, expectedContentType)
+	}
+}
+
+func TestAttachmentResponse(t *testing.T) {
+	r, err := http.NewRequest("GET", "/", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	w := httptest.NewRecorder()
+
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		Attachment(w, r, "file.xml", "Some XML")
+	})
+
+	handler.ServeHTTP(w, r)
+	resp := w.Result()
+
+	expectedStatusCode := http.StatusOK
+	if resp.StatusCode != expectedStatusCode {
+		t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
+	}
+
+	expectedBody := `Some XML`
+	actualBody := w.Body.String()
+	if actualBody != expectedBody {
+		t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, expectedBody)
+	}
+
+	headers := map[string]string{
+		"Content-Type":        "text/xml; charset=utf-8",
+		"Content-Disposition": "attachment; filename=file.xml",
+	}
+
+	for header, expected := range headers {
+		actual := resp.Header.Get(header)
+		if actual != expected {
+			t.Fatalf(`Unexpected header value, got %q instead of %q`, actual, expected)
+		}
+	}
+}
diff --git a/locale/translations.go b/locale/translations.go
index 0c9461ff..cb070831 100755
--- a/locale/translations.go
+++ b/locale/translations.go
@@ -478,7 +478,7 @@ var translations = map[string]string{
     "error.fields_mandatory": "All fields are mandatory.",
     "error.title_required": "The title is mandatory.",
     "error.different_passwords": "Passwords are not the same.",
-    "error.password_min_length": "You must use at least 6 characters.",
+    "error.password_min_length": "The password must have at least 6 characters.",
     "error.settings_mandatory_fields": "The username, theme, language and timezone fields are mandatory.",
     "error.feed_mandatory_fields": "The URL and the category are mandatory.",
     "error.user_mandatory_fields": "The username is mandatory.",
@@ -746,7 +746,7 @@ var translations = map[string]string{
     "error.fields_mandatory": "Tous les champs sont obligatoire.",
     "error.title_required": "Le titre est obligatoire.",
     "error.different_passwords": "Les mots de passe ne sont pas les mêmes.",
-    "error.password_min_length": "Vous devez utiliser au moins 6 caractères.",
+    "error.password_min_length": "Vous devez utiliser au moins 6 caractères pour le mot de passe.",
     "error.settings_mandatory_fields": "Le nom d'utilisateur, le thème, la langue et le fuseau horaire sont obligatoire.",
     "error.feed_mandatory_fields": "L'URL et la catégorie sont obligatoire.",
     "error.user_mandatory_fields": "Le nom d'utilisateur est obligatoire.",
@@ -1985,8 +1985,8 @@ var translations = map[string]string{
 
 var translationsChecksums = map[string]string{
 	"de_DE": "604b7a957e7633da81585553d17ef401326914e2b6605cfe50450b8f4df44310",
-	"en_US": "d979bd262b8631bfde29da6b22f786184f27589b1c7d7760f80d3db198868baf",
-	"fr_FR": "452cd05dfe9b9b797af6037798ae1d0335622e378aa8d97427503040cb5db326",
+	"en_US": "7dcc212a35180ba1800d5dcb5c8455e746a5eb0c3c5c79b3b31ca0eb8dc46356",
+	"fr_FR": "e6ebd22a6c75cebf666e18424d489db254f0b34dc15a7002a574929179efb09a",
 	"nl_NL": "3bf3fd429bdf3e46a0be7f7e89eb06b8272a1833a04aca47ed8dd959fcac13a3",
 	"pl_PL": "6fcf2c429ad68cb99e357825e76bc8f79d9ca3b5d01217beed5e502df2eaa5c9",
 	"ru_RU": "5cd9093807f75f27580a1a20f0b4e4b1a4f2dc3c60b93d4fd53b5b01ecd34d71",
diff --git a/locale/translations/en_US.json b/locale/translations/en_US.json
index 4b8cc364..5d92a8b1 100644
--- a/locale/translations/en_US.json
+++ b/locale/translations/en_US.json
@@ -185,7 +185,7 @@
     "error.fields_mandatory": "All fields are mandatory.",
     "error.title_required": "The title is mandatory.",
     "error.different_passwords": "Passwords are not the same.",
-    "error.password_min_length": "You must use at least 6 characters.",
+    "error.password_min_length": "The password must have at least 6 characters.",
     "error.settings_mandatory_fields": "The username, theme, language and timezone fields are mandatory.",
     "error.feed_mandatory_fields": "The URL and the category are mandatory.",
     "error.user_mandatory_fields": "The username is mandatory.",
diff --git a/locale/translations/fr_FR.json b/locale/translations/fr_FR.json
index 396071ad..4145185e 100644
--- a/locale/translations/fr_FR.json
+++ b/locale/translations/fr_FR.json
@@ -185,7 +185,7 @@
     "error.fields_mandatory": "Tous les champs sont obligatoire.",
     "error.title_required": "Le titre est obligatoire.",
     "error.different_passwords": "Les mots de passe ne sont pas les mêmes.",
-    "error.password_min_length": "Vous devez utiliser au moins 6 caractères.",
+    "error.password_min_length": "Vous devez utiliser au moins 6 caractères pour le mot de passe.",
     "error.settings_mandatory_fields": "Le nom d'utilisateur, le thème, la langue et le fuseau horaire sont obligatoire.",
     "error.feed_mandatory_fields": "L'URL et la catégorie sont obligatoire.",
     "error.user_mandatory_fields": "Le nom d'utilisateur est obligatoire.",
diff --git a/middleware/app_session.go b/middleware/app_session.go
index b505ee1e..1134e399 100644
--- a/middleware/app_session.go
+++ b/middleware/app_session.go
@@ -27,8 +27,7 @@ func (m *Middleware) AppSession(next http.Handler) http.Handler {
 
 			session, err = m.store.CreateSession()
 			if err != nil {
-				logger.Error("[Middleware:AppSession] %v", err)
-				html.ServerError(w, err)
+				html.ServerError(w, r, err)
 				return
 			}
 
@@ -43,7 +42,7 @@ func (m *Middleware) AppSession(next http.Handler) http.Handler {
 
 			if session.Data.CSRF != formValue && session.Data.CSRF != headerValue {
 				logger.Error(`[Middleware:AppSession] Invalid or missing CSRF token: Form="%s", Header="%s"`, formValue, headerValue)
-				html.BadRequest(w, errors.New("invalid or missing CSRF"))
+				html.BadRequest(w, r, errors.New("Invalid or missing CSRF"))
 				return
 			}
 		}
diff --git a/middleware/basic_auth.go b/middleware/basic_auth.go
index 5a7204c8..c897beb2 100644
--- a/middleware/basic_auth.go
+++ b/middleware/basic_auth.go
@@ -22,26 +22,26 @@ func (m *Middleware) BasicAuth(next http.Handler) http.Handler {
 		username, password, authOK := r.BasicAuth()
 		if !authOK {
 			logger.Debug("[Middleware:BasicAuth] No authentication headers sent")
-			json.Unauthorized(w)
+			json.Unauthorized(w, r)
 			return
 		}
 
 		if err := m.store.CheckPassword(username, password); err != nil {
 			logger.Error("[Middleware:BasicAuth] [ClientIP=%s] Invalid username or password: %s", clientIP, username)
-			json.Unauthorized(w)
+			json.Unauthorized(w, r)
 			return
 		}
 
 		user, err := m.store.UserByUsername(username)
 		if err != nil {
 			logger.Error("[Middleware:BasicAuth] %v", err)
-			json.ServerError(w, err)
+			json.ServerError(w, r, err)
 			return
 		}
 
 		if user == nil {
 			logger.Error("[Middleware:BasicAuth] [ClientIP=%s] User not found: %s", clientIP, username)
-			json.Unauthorized(w)
+			json.Unauthorized(w, r)
 			return
 		}
 
diff --git a/middleware/common_headers.go b/middleware/common_headers.go
deleted file mode 100644
index a60969f7..00000000
--- a/middleware/common_headers.go
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2018 Frédéric Guillot. All rights reserved.
-// Use of this source code is governed by the Apache 2.0
-// license that can be found in the LICENSE file.
-
-package middleware // import "miniflux.app/middleware"
-
-import (
-	"net/http"
-)
-
-// CommonHeaders sends common HTTP headers.
-func (m *Middleware) CommonHeaders(next http.Handler) http.Handler {
-	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		w.Header().Set("X-XSS-Protection", "1; mode=block")
-		w.Header().Set("X-Content-Type-Options", "nosniff")
-		w.Header().Set("X-Frame-Options", "DENY")
-		w.Header().Set("Content-Security-Policy", "default-src 'self'; img-src *; media-src *; frame-src *; child-src *")
-
-		if m.cfg.IsHTTPS && m.cfg.HasHSTS() {
-			w.Header().Set("Strict-Transport-Security", "max-age=31536000")
-		}
-
-		next.ServeHTTP(w, r)
-	})
-}
diff --git a/middleware/fever.go b/middleware/fever.go
index 9addf7ee..e4dc1d42 100644
--- a/middleware/fever.go
+++ b/middleware/fever.go
@@ -26,7 +26,7 @@ func (m *Middleware) FeverAuth(next http.Handler) http.Handler {
 		}
 
 		if user == nil {
-			logger.Info("[Middleware:Fever] Fever authentication failure")
+			logger.Info("[Middleware:Fever] No user found with this API key")
 			json.OK(w, r, map[string]int{"api_version": 3, "auth": 0})
 			return
 		}
diff --git a/middleware/header_config.go b/middleware/header_config.go
index 4302ac80..d62d76c6 100644
--- a/middleware/header_config.go
+++ b/middleware/header_config.go
@@ -14,6 +14,11 @@ func (m *Middleware) HeaderConfig(next http.Handler) http.Handler {
 		if r.Header.Get("X-Forwarded-Proto") == "https" {
 			m.cfg.IsHTTPS = true
 		}
+
+		if m.cfg.IsHTTPS && m.cfg.HasHSTS() {
+			w.Header().Set("Strict-Transport-Security", "max-age=31536000")
+		}
+
 		next.ServeHTTP(w, r)
 	})
 }
diff --git a/middleware/user_session.go b/middleware/user_session.go
index bddb47bc..10eeca7d 100644
--- a/middleware/user_session.go
+++ b/middleware/user_session.go
@@ -10,7 +10,7 @@ import (
 
 	"miniflux.app/http/cookie"
 	"miniflux.app/http/request"
-	"miniflux.app/http/response"
+	"miniflux.app/http/response/html"
 	"miniflux.app/http/route"
 	"miniflux.app/logger"
 	"miniflux.app/model"
@@ -28,7 +28,7 @@ func (m *Middleware) UserSession(next http.Handler) http.Handler {
 			if m.isPublicRoute(r) {
 				next.ServeHTTP(w, r)
 			} else {
-				response.Redirect(w, r, route.Path(m.router, "login"))
+				html.Redirect(w, r, route.Path(m.router, "login"))
 			}
 		} else {
 			logger.Debug("[Middleware:UserSession] %s", session)
diff --git a/ui/about.go b/ui/about.go
index e11ab521..abc39a0f 100644
--- a/ui/about.go
+++ b/ui/about.go
@@ -18,7 +18,7 @@ import (
 func (c *Controller) About(w http.ResponseWriter, r *http.Request) {
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
diff --git a/ui/bookmark_entries.go b/ui/bookmark_entries.go
index 08385f68..5e6bb45e 100644
--- a/ui/bookmark_entries.go
+++ b/ui/bookmark_entries.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package ui  // import "miniflux.app/ui"
+package ui // import "miniflux.app/ui"
 
 import (
 	"net/http"
@@ -19,7 +19,7 @@ import (
 func (c *Controller) ShowStarredPage(w http.ResponseWriter, r *http.Request) {
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
@@ -34,13 +34,13 @@ func (c *Controller) ShowStarredPage(w http.ResponseWriter, r *http.Request) {
 
 	entries, err := builder.GetEntries()
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	count, err := builder.CountEntries()
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
diff --git a/ui/category_create.go b/ui/category_create.go
index 20d280a4..bd5039ff 100644
--- a/ui/category_create.go
+++ b/ui/category_create.go
@@ -17,7 +17,7 @@ import (
 func (c *Controller) CreateCategory(w http.ResponseWriter, r *http.Request) {
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
diff --git a/ui/category_edit.go b/ui/category_edit.go
index b99d2a04..e0375beb 100644
--- a/ui/category_edit.go
+++ b/ui/category_edit.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package ui  // import "miniflux.app/ui"
+package ui // import "miniflux.app/ui"
 
 import (
 	"net/http"
@@ -21,19 +21,19 @@ func (c *Controller) EditCategory(w http.ResponseWriter, r *http.Request) {
 
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	categoryID := request.RouteInt64Param(r, "categoryID")
 	category, err := c.store.Category(request.UserID(r), categoryID)
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	if category == nil {
-		html.NotFound(w)
+		html.NotFound(w, r)
 		return
 	}
 
diff --git a/ui/category_entries.go b/ui/category_entries.go
index caa98cde..fe5f6385 100644
--- a/ui/category_entries.go
+++ b/ui/category_entries.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package ui  // import "miniflux.app/ui"
+package ui // import "miniflux.app/ui"
 
 import (
 	"net/http"
@@ -19,19 +19,19 @@ import (
 func (c *Controller) CategoryEntries(w http.ResponseWriter, r *http.Request) {
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	categoryID := request.RouteInt64Param(r, "categoryID")
 	category, err := c.store.Category(request.UserID(r), categoryID)
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	if category == nil {
-		html.NotFound(w)
+		html.NotFound(w, r)
 		return
 	}
 
@@ -46,13 +46,13 @@ func (c *Controller) CategoryEntries(w http.ResponseWriter, r *http.Request) {
 
 	entries, err := builder.GetEntries()
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	count, err := builder.CountEntries()
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
diff --git a/ui/category_list.go b/ui/category_list.go
index 3dc17cad..f4a3651e 100644
--- a/ui/category_list.go
+++ b/ui/category_list.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package ui  // import "miniflux.app/ui"
+package ui // import "miniflux.app/ui"
 
 import (
 	"net/http"
@@ -17,13 +17,13 @@ import (
 func (c *Controller) CategoryList(w http.ResponseWriter, r *http.Request) {
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	categories, err := c.store.CategoriesWithFeedCount(user.ID)
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
diff --git a/ui/category_remove.go b/ui/category_remove.go
index b424af53..f8a38dc6 100644
--- a/ui/category_remove.go
+++ b/ui/category_remove.go
@@ -8,7 +8,6 @@ import (
 	"net/http"
 
 	"miniflux.app/http/request"
-	"miniflux.app/http/response"
 	"miniflux.app/http/response/html"
 	"miniflux.app/http/route"
 )
@@ -17,26 +16,26 @@ import (
 func (c *Controller) RemoveCategory(w http.ResponseWriter, r *http.Request) {
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	categoryID := request.RouteInt64Param(r, "categoryID")
 	category, err := c.store.Category(request.UserID(r), categoryID)
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	if category == nil {
-		html.NotFound(w)
+		html.NotFound(w, r)
 		return
 	}
 
 	if err := c.store.RemoveCategory(user.ID, category.ID); err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
-	response.Redirect(w, r, route.Path(c.router, "categories"))
+	html.Redirect(w, r, route.Path(c.router, "categories"))
 }
diff --git a/ui/category_save.go b/ui/category_save.go
index ec85cd39..d115d0ab 100644
--- a/ui/category_save.go
+++ b/ui/category_save.go
@@ -2,12 +2,11 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package ui  // import "miniflux.app/ui"
+package ui // import "miniflux.app/ui"
 
 import (
 	"net/http"
 
-	"miniflux.app/http/response"
 	"miniflux.app/http/response/html"
 	"miniflux.app/http/route"
 	"miniflux.app/http/request"
@@ -22,7 +21,7 @@ import (
 func (c *Controller) SaveCategory(w http.ResponseWriter, r *http.Request) {
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
@@ -44,7 +43,7 @@ func (c *Controller) SaveCategory(w http.ResponseWriter, r *http.Request) {
 
 	duplicateCategory, err := c.store.CategoryByTitle(user.ID, categoryForm.Title)
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
@@ -66,5 +65,5 @@ func (c *Controller) SaveCategory(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	response.Redirect(w, r, route.Path(c.router, "categories"))
+	html.Redirect(w, r, route.Path(c.router, "categories"))
 }
diff --git a/ui/category_update.go b/ui/category_update.go
index 90672c01..6ef56484 100644
--- a/ui/category_update.go
+++ b/ui/category_update.go
@@ -2,13 +2,12 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package ui  // import "miniflux.app/ui"
+package ui // import "miniflux.app/ui"
 
 import (
 	"net/http"
 
 	"miniflux.app/http/request"
-	"miniflux.app/http/response"
 	"miniflux.app/http/response/html"
 	"miniflux.app/http/route"
 	"miniflux.app/logger"
@@ -21,19 +20,19 @@ import (
 func (c *Controller) UpdateCategory(w http.ResponseWriter, r *http.Request) {
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	categoryID := request.RouteInt64Param(r, "categoryID")
 	category, err := c.store.Category(request.UserID(r), categoryID)
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	if category == nil {
-		html.NotFound(w)
+		html.NotFound(w, r)
 		return
 	}
 
@@ -68,5 +67,5 @@ func (c *Controller) UpdateCategory(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	response.Redirect(w, r, route.Path(c.router, "categories"))
+	html.Redirect(w, r, route.Path(c.router, "categories"))
 }
diff --git a/ui/entry_bookmark.go b/ui/entry_bookmark.go
index 7c42a5cd..895c2bc3 100644
--- a/ui/entry_bookmark.go
+++ b/ui/entry_bookmark.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package ui  // import "miniflux.app/ui"
+package ui // import "miniflux.app/ui"
 
 import (
 	"net/http"
@@ -10,7 +10,6 @@ import (
 	"miniflux.app/http/request"
 	"miniflux.app/http/response/html"
 	"miniflux.app/http/route"
-	"miniflux.app/logger"
 	"miniflux.app/model"
 	"miniflux.app/storage"
 	"miniflux.app/ui/session"
@@ -21,7 +20,7 @@ import (
 func (c *Controller) ShowStarredEntry(w http.ResponseWriter, r *http.Request) {
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
@@ -32,20 +31,19 @@ func (c *Controller) ShowStarredEntry(w http.ResponseWriter, r *http.Request) {
 
 	entry, err := builder.GetEntry()
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	if entry == nil {
-		html.NotFound(w)
+		html.NotFound(w, r)
 		return
 	}
 
 	if entry.Status == model.EntryStatusUnread {
 		err = c.store.SetEntriesStatus(user.ID, []int64{entry.ID}, model.EntryStatusRead)
 		if err != nil {
-			logger.Error("[Controller:ShowReadEntry] %v", err)
-			html.ServerError(w, nil)
+			html.ServerError(w, r, err)
 			return
 		}
 
@@ -56,7 +54,7 @@ func (c *Controller) ShowStarredEntry(w http.ResponseWriter, r *http.Request) {
 	entryPaginationBuilder.WithStarred()
 	prevEntry, nextEntry, err := entryPaginationBuilder.Entries()
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
diff --git a/ui/entry_category.go b/ui/entry_category.go
index 283f015d..d11248be 100644
--- a/ui/entry_category.go
+++ b/ui/entry_category.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package ui  // import "miniflux.app/ui"
+package ui // import "miniflux.app/ui"
 
 import (
 	"net/http"
@@ -10,7 +10,6 @@ import (
 	"miniflux.app/http/request"
 	"miniflux.app/http/response/html"
 	"miniflux.app/http/route"
-	"miniflux.app/logger"
 	"miniflux.app/model"
 	"miniflux.app/storage"
 	"miniflux.app/ui/session"
@@ -21,7 +20,7 @@ import (
 func (c *Controller) ShowCategoryEntry(w http.ResponseWriter, r *http.Request) {
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
@@ -35,20 +34,19 @@ func (c *Controller) ShowCategoryEntry(w http.ResponseWriter, r *http.Request) {
 
 	entry, err := builder.GetEntry()
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	if entry == nil {
-		html.NotFound(w)
+		html.NotFound(w, r)
 		return
 	}
 
 	if entry.Status == model.EntryStatusUnread {
 		err = c.store.SetEntriesStatus(user.ID, []int64{entry.ID}, model.EntryStatusRead)
 		if err != nil {
-			logger.Error("[Controller:ShowCategoryEntry] %v", err)
-			html.ServerError(w, nil)
+			html.ServerError(w, r, err)
 			return
 		}
 
@@ -59,7 +57,7 @@ func (c *Controller) ShowCategoryEntry(w http.ResponseWriter, r *http.Request) {
 	entryPaginationBuilder.WithCategoryID(categoryID)
 	prevEntry, nextEntry, err := entryPaginationBuilder.Entries()
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
diff --git a/ui/entry_feed.go b/ui/entry_feed.go
index 86dd2c92..9c28c676 100644
--- a/ui/entry_feed.go
+++ b/ui/entry_feed.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package ui  // import "miniflux.app/ui"
+package ui // import "miniflux.app/ui"
 
 import (
 	"net/http"
@@ -10,7 +10,6 @@ import (
 	"miniflux.app/http/request"
 	"miniflux.app/http/response/html"
 	"miniflux.app/http/route"
-	"miniflux.app/logger"
 	"miniflux.app/model"
 	"miniflux.app/storage"
 	"miniflux.app/ui/session"
@@ -21,7 +20,7 @@ import (
 func (c *Controller) ShowFeedEntry(w http.ResponseWriter, r *http.Request) {
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
@@ -35,20 +34,19 @@ func (c *Controller) ShowFeedEntry(w http.ResponseWriter, r *http.Request) {
 
 	entry, err := builder.GetEntry()
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	if entry == nil {
-		html.NotFound(w)
+		html.NotFound(w, r)
 		return
 	}
 
 	if entry.Status == model.EntryStatusUnread {
 		err = c.store.SetEntriesStatus(user.ID, []int64{entry.ID}, model.EntryStatusRead)
 		if err != nil {
-			logger.Error("[Controller:ShowFeedEntry] %v", err)
-			html.ServerError(w, nil)
+			html.ServerError(w, r, err)
 			return
 		}
 
@@ -59,7 +57,7 @@ func (c *Controller) ShowFeedEntry(w http.ResponseWriter, r *http.Request) {
 	entryPaginationBuilder.WithFeedID(feedID)
 	prevEntry, nextEntry, err := entryPaginationBuilder.Entries()
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
diff --git a/ui/entry_read.go b/ui/entry_read.go
index eeaca8e5..208ae3bf 100644
--- a/ui/entry_read.go
+++ b/ui/entry_read.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package ui  // import "miniflux.app/ui"
+package ui // import "miniflux.app/ui"
 
 import (
 	"net/http"
@@ -20,7 +20,7 @@ import (
 func (c *Controller) ShowReadEntry(w http.ResponseWriter, r *http.Request) {
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
@@ -31,12 +31,12 @@ func (c *Controller) ShowReadEntry(w http.ResponseWriter, r *http.Request) {
 
 	entry, err := builder.GetEntry()
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	if entry == nil {
-		html.NotFound(w)
+		html.NotFound(w, r)
 		return
 	}
 
@@ -44,7 +44,7 @@ func (c *Controller) ShowReadEntry(w http.ResponseWriter, r *http.Request) {
 	entryPaginationBuilder.WithStatus(model.EntryStatusRead)
 	prevEntry, nextEntry, err := entryPaginationBuilder.Entries()
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
diff --git a/ui/entry_save.go b/ui/entry_save.go
index 1f846bad..93910c07 100644
--- a/ui/entry_save.go
+++ b/ui/entry_save.go
@@ -5,7 +5,6 @@
 package ui // import "miniflux.app/ui"
 
 import (
-	"errors"
 	"net/http"
 
 	"miniflux.app/http/request"
@@ -23,18 +22,18 @@ func (c *Controller) SaveEntry(w http.ResponseWriter, r *http.Request) {
 
 	entry, err := builder.GetEntry()
 	if err != nil {
-		json.ServerError(w, err)
+		json.ServerError(w, r, err)
 		return
 	}
 
 	if entry == nil {
-		json.NotFound(w, errors.New("Entry not found"))
+		json.NotFound(w, r)
 		return
 	}
 
 	settings, err := c.store.Integration(request.UserID(r))
 	if err != nil {
-		json.ServerError(w, err)
+		json.ServerError(w, r, err)
 		return
 	}
 
@@ -42,5 +41,5 @@ func (c *Controller) SaveEntry(w http.ResponseWriter, r *http.Request) {
 		integration.SendEntry(c.cfg, entry, settings)
 	}()
 
-	json.Created(w, map[string]string{"message": "saved"})
+	json.Created(w, r, map[string]string{"message": "saved"})
 }
diff --git a/ui/entry_scraper.go b/ui/entry_scraper.go
index 4c2d58cb..c35d9497 100644
--- a/ui/entry_scraper.go
+++ b/ui/entry_scraper.go
@@ -2,10 +2,9 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package ui  // import "miniflux.app/ui"
+package ui // import "miniflux.app/ui"
 
 import (
-	"errors"
 	"net/http"
 
 	"miniflux.app/http/request"
@@ -24,18 +23,18 @@ func (c *Controller) FetchContent(w http.ResponseWriter, r *http.Request) {
 
 	entry, err := builder.GetEntry()
 	if err != nil {
-		json.ServerError(w, err)
+		json.ServerError(w, r, err)
 		return
 	}
 
 	if entry == nil {
-		json.NotFound(w, errors.New("Entry not found"))
+		json.NotFound(w, r)
 		return
 	}
 
 	content, err := scraper.Fetch(entry.URL, entry.Feed.ScraperRules, entry.Feed.UserAgent)
 	if err != nil {
-		json.ServerError(w, err)
+		json.ServerError(w, r, err)
 		return
 	}
 
diff --git a/ui/entry_search.go b/ui/entry_search.go
index 8acf1031..d083b353 100644
--- a/ui/entry_search.go
+++ b/ui/entry_search.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package ui  // import "miniflux.app/ui"
+package ui // import "miniflux.app/ui"
 
 import (
 	"net/http"
@@ -21,7 +21,7 @@ import (
 func (c *Controller) ShowSearchEntry(w http.ResponseWriter, r *http.Request) {
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
@@ -34,12 +34,12 @@ func (c *Controller) ShowSearchEntry(w http.ResponseWriter, r *http.Request) {
 
 	entry, err := builder.GetEntry()
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	if entry == nil {
-		html.NotFound(w)
+		html.NotFound(w, r)
 		return
 	}
 
@@ -47,7 +47,7 @@ func (c *Controller) ShowSearchEntry(w http.ResponseWriter, r *http.Request) {
 		err = c.store.SetEntriesStatus(user.ID, []int64{entry.ID}, model.EntryStatusRead)
 		if err != nil {
 			logger.Error("[Controller:ShowSearchEntry] %v", err)
-			html.ServerError(w, nil)
+			html.ServerError(w, r, err)
 			return
 		}
 
@@ -58,7 +58,7 @@ func (c *Controller) ShowSearchEntry(w http.ResponseWriter, r *http.Request) {
 	entryPaginationBuilder.WithSearchQuery(searchQuery)
 	prevEntry, nextEntry, err := entryPaginationBuilder.Entries()
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
diff --git a/ui/entry_toggle_bookmark.go b/ui/entry_toggle_bookmark.go
index 14a2c753..9f7a3e77 100644
--- a/ui/entry_toggle_bookmark.go
+++ b/ui/entry_toggle_bookmark.go
@@ -9,15 +9,13 @@ import (
 
 	"miniflux.app/http/request"
 	"miniflux.app/http/response/json"
-	"miniflux.app/logger"
 )
 
 // ToggleBookmark handles Ajax request to toggle bookmark value.
 func (c *Controller) ToggleBookmark(w http.ResponseWriter, r *http.Request) {
 	entryID := request.RouteInt64Param(r, "entryID")
 	if err := c.store.ToggleBookmark(request.UserID(r), entryID); err != nil {
-		logger.Error("[Controller:ToggleBookmark] %v", err)
-		json.ServerError(w, nil)
+		json.ServerError(w, r, err)
 		return
 	}
 
diff --git a/ui/entry_unread.go b/ui/entry_unread.go
index 4ef57313..37e9592e 100644
--- a/ui/entry_unread.go
+++ b/ui/entry_unread.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package ui  // import "miniflux.app/ui"
+package ui // import "miniflux.app/ui"
 
 import (
 	"net/http"
@@ -10,7 +10,6 @@ import (
 	"miniflux.app/http/request"
 	"miniflux.app/http/response/html"
 	"miniflux.app/http/route"
-	"miniflux.app/logger"
 	"miniflux.app/model"
 	"miniflux.app/storage"
 	"miniflux.app/ui/session"
@@ -21,7 +20,7 @@ import (
 func (c *Controller) ShowUnreadEntry(w http.ResponseWriter, r *http.Request) {
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
@@ -32,12 +31,12 @@ func (c *Controller) ShowUnreadEntry(w http.ResponseWriter, r *http.Request) {
 
 	entry, err := builder.GetEntry()
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	if entry == nil {
-		html.NotFound(w)
+		html.NotFound(w, r)
 		return
 	}
 
@@ -45,8 +44,7 @@ func (c *Controller) ShowUnreadEntry(w http.ResponseWriter, r *http.Request) {
 	if entry.Status == model.EntryStatusRead {
 		err = c.store.SetEntriesStatus(user.ID, []int64{entry.ID}, model.EntryStatusUnread)
 		if err != nil {
-			logger.Error("[Controller:ShowUnreadEntry] %v", err)
-			html.ServerError(w, nil)
+			html.ServerError(w, r, err)
 			return
 		}
 	}
@@ -55,7 +53,7 @@ func (c *Controller) ShowUnreadEntry(w http.ResponseWriter, r *http.Request) {
 	entryPaginationBuilder.WithStatus(model.EntryStatusUnread)
 	prevEntry, nextEntry, err := entryPaginationBuilder.Entries()
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
@@ -72,8 +70,7 @@ func (c *Controller) ShowUnreadEntry(w http.ResponseWriter, r *http.Request) {
 	// Always mark the entry as read after fetching the pagination.
 	err = c.store.SetEntriesStatus(user.ID, []int64{entry.ID}, model.EntryStatusRead)
 	if err != nil {
-		logger.Error("[Controller:ShowUnreadEntry] %v", err)
-		html.ServerError(w, nil)
+		html.ServerError(w, r, err)
 		return
 	}
 	entry.Status = model.EntryStatusRead
diff --git a/ui/entry_update_status.go b/ui/entry_update_status.go
index 8e9de4de..6c5cb127 100644
--- a/ui/entry_update_status.go
+++ b/ui/entry_update_status.go
@@ -10,27 +10,24 @@ import (
 
 	"miniflux.app/http/request"
 	"miniflux.app/http/response/json"
-	"miniflux.app/logger"
 )
 
 // UpdateEntriesStatus updates the status for a list of entries.
 func (c *Controller) UpdateEntriesStatus(w http.ResponseWriter, r *http.Request) {
 	entryIDs, status, err := decodeEntryStatusPayload(r.Body)
 	if err != nil {
-		logger.Error("[Controller:UpdateEntryStatus] %v", err)
-		json.BadRequest(w, nil)
+		json.BadRequest(w, r, err)
 		return
 	}
 
 	if len(entryIDs) == 0 {
-		json.BadRequest(w, errors.New("The list of entryID is empty"))
+		json.BadRequest(w, r, errors.New("The list of entry IDs is empty"))
 		return
 	}
 
 	err = c.store.SetEntriesStatus(request.UserID(r), entryIDs, status)
 	if err != nil {
-		logger.Error("[Controller:UpdateEntryStatus] %v", err)
-		json.ServerError(w, nil)
+		json.ServerError(w, r, err)
 		return
 	}
 
diff --git a/ui/feed_edit.go b/ui/feed_edit.go
index 8b1b3cb6..330817bf 100644
--- a/ui/feed_edit.go
+++ b/ui/feed_edit.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package ui  // import "miniflux.app/ui"
+package ui // import "miniflux.app/ui"
 
 import (
 	"net/http"
@@ -19,25 +19,25 @@ import (
 func (c *Controller) EditFeed(w http.ResponseWriter, r *http.Request) {
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	feedID := request.RouteInt64Param(r, "feedID")
 	feed, err := c.store.FeedByID(user.ID, feedID)
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	if feed == nil {
-		html.NotFound(w)
+		html.NotFound(w, r)
 		return
 	}
 
 	categories, err := c.store.Categories(user.ID)
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
diff --git a/ui/feed_entries.go b/ui/feed_entries.go
index 06a298ba..685b5c16 100644
--- a/ui/feed_entries.go
+++ b/ui/feed_entries.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package ui  // import "miniflux.app/ui"
+package ui // import "miniflux.app/ui"
 
 import (
 	"net/http"
@@ -19,19 +19,19 @@ import (
 func (c *Controller) ShowFeedEntries(w http.ResponseWriter, r *http.Request) {
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	feedID := request.RouteInt64Param(r, "feedID")
 	feed, err := c.store.FeedByID(user.ID, feedID)
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	if feed == nil {
-		html.NotFound(w)
+		html.NotFound(w, r)
 		return
 	}
 
@@ -46,13 +46,13 @@ func (c *Controller) ShowFeedEntries(w http.ResponseWriter, r *http.Request) {
 
 	entries, err := builder.GetEntries()
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	count, err := builder.CountEntries()
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
diff --git a/ui/feed_icon.go b/ui/feed_icon.go
index 0aa70890..63aa050f 100644
--- a/ui/feed_icon.go
+++ b/ui/feed_icon.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package ui  // import "miniflux.app/ui"
+package ui // import "miniflux.app/ui"
 
 import (
 	"net/http"
@@ -18,14 +18,19 @@ func (c *Controller) ShowIcon(w http.ResponseWriter, r *http.Request) {
 	iconID := request.RouteInt64Param(r, "iconID")
 	icon, err := c.store.IconByID(iconID)
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	if icon == nil {
-		html.NotFound(w)
+		html.NotFound(w, r)
 		return
 	}
 
-	response.Cache(w, r, icon.MimeType, icon.Hash, icon.Content, 72*time.Hour)
+	response.New(w, r).WithCaching(icon.Hash, 72*time.Hour, func(b *response.Builder) {
+		b.WithHeader("Content-Type", icon.MimeType)
+		b.WithBody(icon.Content)
+		b.WithoutCompression()
+		b.Write()
+	})
 }
diff --git a/ui/feed_list.go b/ui/feed_list.go
index 9fe4aac6..ac5a97c3 100644
--- a/ui/feed_list.go
+++ b/ui/feed_list.go
@@ -17,13 +17,13 @@ import (
 func (c *Controller) ShowFeedsPage(w http.ResponseWriter, r *http.Request) {
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	feeds, err := c.store.Feeds(user.ID)
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
diff --git a/ui/feed_refresh.go b/ui/feed_refresh.go
index df93c6ec..0da4eb8e 100644
--- a/ui/feed_refresh.go
+++ b/ui/feed_refresh.go
@@ -2,13 +2,12 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package ui  // import "miniflux.app/ui"
+package ui // import "miniflux.app/ui"
 
 import (
 	"net/http"
 
 	"miniflux.app/http/request"
-	"miniflux.app/http/response"
 	"miniflux.app/http/response/html"
 	"miniflux.app/http/route"
 	"miniflux.app/logger"
@@ -21,7 +20,7 @@ func (c *Controller) RefreshFeed(w http.ResponseWriter, r *http.Request) {
 		logger.Error("[Controller:RefreshFeed] %v", err)
 	}
 
-	response.Redirect(w, r, route.Path(c.router, "feedEntries", "feedID", feedID))
+	html.Redirect(w, r, route.Path(c.router, "feedEntries", "feedID", feedID))
 }
 
 // RefreshAllFeeds refresh all feeds in the background for the current user.
@@ -29,7 +28,7 @@ func (c *Controller) RefreshAllFeeds(w http.ResponseWriter, r *http.Request) {
 	userID := request.UserID(r)
 	jobs, err := c.store.NewUserBatch(userID, c.store.CountFeeds(userID))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
@@ -37,5 +36,5 @@ func (c *Controller) RefreshAllFeeds(w http.ResponseWriter, r *http.Request) {
 		c.pool.Push(jobs)
 	}()
 
-	response.Redirect(w, r, route.Path(c.router, "feeds"))
+	html.Redirect(w, r, route.Path(c.router, "feeds"))
 }
diff --git a/ui/feed_remove.go b/ui/feed_remove.go
index d1ab01af..41fc5b08 100644
--- a/ui/feed_remove.go
+++ b/ui/feed_remove.go
@@ -8,7 +8,6 @@ import (
 	"net/http"
 
 	"miniflux.app/http/request"
-	"miniflux.app/http/response"
 	"miniflux.app/http/response/html"
 	"miniflux.app/http/route"
 )
@@ -17,9 +16,9 @@ import (
 func (c *Controller) RemoveFeed(w http.ResponseWriter, r *http.Request) {
 	feedID := request.RouteInt64Param(r, "feedID")
 	if err := c.store.RemoveFeed(request.UserID(r), feedID); err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
-	response.Redirect(w, r, route.Path(c.router, "feeds"))
+	html.Redirect(w, r, route.Path(c.router, "feeds"))
 }
diff --git a/ui/feed_update.go b/ui/feed_update.go
index 6cc4776a..66b6d40e 100644
--- a/ui/feed_update.go
+++ b/ui/feed_update.go
@@ -2,14 +2,13 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package ui  // import "miniflux.app/ui"
+package ui // import "miniflux.app/ui"
 
 import (
 	"net/http"
 
 	"miniflux.app/http/client"
 	"miniflux.app/http/request"
-	"miniflux.app/http/response"
 	"miniflux.app/http/response/html"
 	"miniflux.app/http/route"
 	"miniflux.app/logger"
@@ -22,25 +21,25 @@ import (
 func (c *Controller) UpdateFeed(w http.ResponseWriter, r *http.Request) {
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	feedID := request.RouteInt64Param(r, "feedID")
 	feed, err := c.store.FeedByID(user.ID, feedID)
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	if feed == nil {
-		html.NotFound(w)
+		html.NotFound(w, r)
 		return
 	}
 
 	categories, err := c.store.Categories(user.ID)
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
@@ -71,5 +70,5 @@ func (c *Controller) UpdateFeed(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	response.Redirect(w, r, route.Path(c.router, "feedEntries", "feedID", feed.ID))
+	html.Redirect(w, r, route.Path(c.router, "feedEntries", "feedID", feed.ID))
 }
diff --git a/ui/history_entries.go b/ui/history_entries.go
index 25310a9d..8449240a 100644
--- a/ui/history_entries.go
+++ b/ui/history_entries.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package ui  // import "miniflux.app/ui"
+package ui // import "miniflux.app/ui"
 
 import (
 	"net/http"
@@ -19,7 +19,7 @@ import (
 func (c *Controller) ShowHistoryPage(w http.ResponseWriter, r *http.Request) {
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
@@ -33,13 +33,13 @@ func (c *Controller) ShowHistoryPage(w http.ResponseWriter, r *http.Request) {
 
 	entries, err := builder.GetEntries()
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	count, err := builder.CountEntries()
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
diff --git a/ui/history_flush.go b/ui/history_flush.go
index 5bb8910e..6c6907b8 100644
--- a/ui/history_flush.go
+++ b/ui/history_flush.go
@@ -8,7 +8,6 @@ import (
 	"net/http"
 
 	"miniflux.app/http/request"
-	"miniflux.app/http/response"
 	"miniflux.app/http/response/html"
 	"miniflux.app/http/route"
 )
@@ -17,9 +16,9 @@ import (
 func (c *Controller) FlushHistory(w http.ResponseWriter, r *http.Request) {
 	err := c.store.FlushHistory(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
-	response.Redirect(w, r, route.Path(c.router, "history"))
+	html.Redirect(w, r, route.Path(c.router, "history"))
 }
diff --git a/ui/integration_pocket.go b/ui/integration_pocket.go
index 407731e6..47a975c7 100644
--- a/ui/integration_pocket.go
+++ b/ui/integration_pocket.go
@@ -7,9 +7,8 @@ package ui  // import "miniflux.app/ui"
 import (
 	"net/http"
 
-	"miniflux.app/http/response"
-	"miniflux.app/http/request"
 	"miniflux.app/http/response/html"
+	"miniflux.app/http/request"
 	"miniflux.app/http/route"
 	"miniflux.app/integration/pocket"
 	"miniflux.app/locale"
@@ -22,13 +21,13 @@ func (c *Controller) PocketAuthorize(w http.ResponseWriter, r *http.Request) {
 	printer := locale.NewPrinter(request.UserLanguage(r))
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	integration, err := c.store.Integration(user.ID)
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
@@ -39,12 +38,12 @@ func (c *Controller) PocketAuthorize(w http.ResponseWriter, r *http.Request) {
 	if err != nil {
 		logger.Error("[Pocket:Authorize] %v", err)
 		sess.NewFlashErrorMessage(printer.Printf("error.pocket_request_token"))
-		response.Redirect(w, r, route.Path(c.router, "integrations"))
+		html.Redirect(w, r, route.Path(c.router, "integrations"))
 		return
 	}
 
 	sess.SetPocketRequestToken(requestToken)
-	response.Redirect(w, r, connector.AuthorizationURL(requestToken, redirectURL))
+	html.Redirect(w, r, connector.AuthorizationURL(requestToken, redirectURL))
 }
 
 // PocketCallback saves the personal access token after the authorization step.
@@ -54,13 +53,13 @@ func (c *Controller) PocketCallback(w http.ResponseWriter, r *http.Request) {
 
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	integration, err := c.store.Integration(user.ID)
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
@@ -69,7 +68,7 @@ func (c *Controller) PocketCallback(w http.ResponseWriter, r *http.Request) {
 	if err != nil {
 		logger.Error("[Pocket:Callback] %v", err)
 		sess.NewFlashErrorMessage(printer.Printf("error.pocket_access_token"))
-		response.Redirect(w, r, route.Path(c.router, "integrations"))
+		html.Redirect(w, r, route.Path(c.router, "integrations"))
 		return
 	}
 
@@ -78,10 +77,10 @@ func (c *Controller) PocketCallback(w http.ResponseWriter, r *http.Request) {
 
 	err = c.store.UpdateIntegration(integration)
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	sess.NewFlashMessage(printer.Printf("alert.pocket_linked"))
-	response.Redirect(w, r, route.Path(c.router, "integrations"))
+	html.Redirect(w, r, route.Path(c.router, "integrations"))
 }
diff --git a/ui/integration_show.go b/ui/integration_show.go
index 1c8adce1..3cea8305 100644
--- a/ui/integration_show.go
+++ b/ui/integration_show.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package ui  // import "miniflux.app/ui"
+package ui // import "miniflux.app/ui"
 
 import (
 	"net/http"
@@ -18,13 +18,13 @@ import (
 func (c *Controller) ShowIntegrations(w http.ResponseWriter, r *http.Request) {
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	integration, err := c.store.Integration(user.ID)
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
diff --git a/ui/integration_update.go b/ui/integration_update.go
index f470e4bd..5661daff 100644
--- a/ui/integration_update.go
+++ b/ui/integration_update.go
@@ -2,16 +2,15 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package ui  // import "miniflux.app/ui"
+package ui // import "miniflux.app/ui"
 
 import (
 	"crypto/md5"
 	"fmt"
 	"net/http"
 
-	"miniflux.app/http/response"
-	"miniflux.app/http/request"
 	"miniflux.app/http/response/html"
+	"miniflux.app/http/request"
 	"miniflux.app/http/route"
 	"miniflux.app/locale"
 	"miniflux.app/ui/form"
@@ -24,13 +23,13 @@ func (c *Controller) UpdateIntegration(w http.ResponseWriter, r *http.Request) {
 	sess := session.New(c.store, request.SessionID(r))
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	integration, err := c.store.Integration(user.ID)
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
@@ -39,7 +38,7 @@ func (c *Controller) UpdateIntegration(w http.ResponseWriter, r *http.Request) {
 
 	if integration.FeverUsername != "" && c.store.HasDuplicateFeverUsername(user.ID, integration.FeverUsername) {
 		sess.NewFlashErrorMessage(printer.Printf("error.duplicate_fever_username"))
-		response.Redirect(w, r, route.Path(c.router, "integrations"))
+		html.Redirect(w, r, route.Path(c.router, "integrations"))
 		return
 	}
 
@@ -51,10 +50,10 @@ func (c *Controller) UpdateIntegration(w http.ResponseWriter, r *http.Request) {
 
 	err = c.store.UpdateIntegration(integration)
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	sess.NewFlashMessage(printer.Printf("alert.prefs_saved"))
-	response.Redirect(w, r, route.Path(c.router, "integrations"))
+	html.Redirect(w, r, route.Path(c.router, "integrations"))
 }
diff --git a/ui/login_check.go b/ui/login_check.go
index 98d2c4e4..73a17362 100644
--- a/ui/login_check.go
+++ b/ui/login_check.go
@@ -5,7 +5,6 @@ import (
 
 	"miniflux.app/http/cookie"
 	"miniflux.app/http/request"
-	"miniflux.app/http/response"
 	"miniflux.app/http/response/html"
 	"miniflux.app/http/route"
 	"miniflux.app/logger"
@@ -38,7 +37,7 @@ func (c *Controller) CheckLogin(w http.ResponseWriter, r *http.Request) {
 
 	sessionToken, userID, err := c.store.CreateUserSession(authForm.Username, r.UserAgent(), clientIP)
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
@@ -47,7 +46,7 @@ func (c *Controller) CheckLogin(w http.ResponseWriter, r *http.Request) {
 
 	user, err := c.store.UserByID(userID)
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
@@ -61,5 +60,5 @@ func (c *Controller) CheckLogin(w http.ResponseWriter, r *http.Request) {
 		c.cfg.BasePath(),
 	))
 
-	response.Redirect(w, r, route.Path(c.router, "unread"))
+	html.Redirect(w, r, route.Path(c.router, "unread"))
 }
diff --git a/ui/login_show.go b/ui/login_show.go
index 82c19ec9..890f9db6 100644
--- a/ui/login_show.go
+++ b/ui/login_show.go
@@ -2,14 +2,13 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package ui  // import "miniflux.app/ui"
+package ui // import "miniflux.app/ui"
 
 import (
 	"net/http"
 
-	"miniflux.app/http/response"
-	"miniflux.app/http/request"
 	"miniflux.app/http/response/html"
+	"miniflux.app/http/request"
 	"miniflux.app/http/route"
 	"miniflux.app/ui/session"
 	"miniflux.app/ui/view"
@@ -18,7 +17,7 @@ import (
 // ShowLoginPage shows the login form.
 func (c *Controller) ShowLoginPage(w http.ResponseWriter, r *http.Request) {
 	if request.IsAuthenticated(r) {
-		response.Redirect(w, r, route.Path(c.router, "unread"))
+		html.Redirect(w, r, route.Path(c.router, "unread"))
 		return
 	}
 
diff --git a/ui/logout.go b/ui/logout.go
index 0c8e9e7b..59f8af2d 100644
--- a/ui/logout.go
+++ b/ui/logout.go
@@ -2,14 +2,13 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package ui  // import "miniflux.app/ui"
+package ui // import "miniflux.app/ui"
 
 import (
 	"net/http"
 
 	"miniflux.app/http/cookie"
 	"miniflux.app/http/request"
-	"miniflux.app/http/response"
 	"miniflux.app/http/response/html"
 	"miniflux.app/http/route"
 	"miniflux.app/logger"
@@ -21,7 +20,7 @@ func (c *Controller) Logout(w http.ResponseWriter, r *http.Request) {
 	sess := session.New(c.store, request.SessionID(r))
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
@@ -38,5 +37,5 @@ func (c *Controller) Logout(w http.ResponseWriter, r *http.Request) {
 		c.cfg.BasePath(),
 	))
 
-	response.Redirect(w, r, route.Path(c.router, "login"))
+	html.Redirect(w, r, route.Path(c.router, "login"))
 }
diff --git a/ui/oauth2.go b/ui/oauth2.go
index 3f964a62..c5d594fc 100644
--- a/ui/oauth2.go
+++ b/ui/oauth2.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package ui  // import "miniflux.app/ui"
+package ui // import "miniflux.app/ui"
 
 import (
 	"miniflux.app/config"
diff --git a/ui/oauth2_callback.go b/ui/oauth2_callback.go
index 1902d6e1..feaccc4a 100644
--- a/ui/oauth2_callback.go
+++ b/ui/oauth2_callback.go
@@ -2,14 +2,13 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package ui  // import "miniflux.app/ui"
+package ui // import "miniflux.app/ui"
 
 import (
 	"net/http"
 
 	"miniflux.app/http/cookie"
 	"miniflux.app/http/request"
-	"miniflux.app/http/response"
 	"miniflux.app/http/response/html"
 	"miniflux.app/http/route"
 	"miniflux.app/locale"
@@ -26,71 +25,71 @@ func (c *Controller) OAuth2Callback(w http.ResponseWriter, r *http.Request) {
 	provider := request.RouteStringParam(r, "provider")
 	if provider == "" {
 		logger.Error("[OAuth2] Invalid or missing provider")
-		response.Redirect(w, r, route.Path(c.router, "login"))
+		html.Redirect(w, r, route.Path(c.router, "login"))
 		return
 	}
 
 	code := request.QueryStringParam(r, "code", "")
 	if code == "" {
 		logger.Error("[OAuth2] No code received on callback")
-		response.Redirect(w, r, route.Path(c.router, "login"))
+		html.Redirect(w, r, route.Path(c.router, "login"))
 		return
 	}
 
 	state := request.QueryStringParam(r, "state", "")
 	if state == "" || state != request.OAuth2State(r) {
 		logger.Error(`[OAuth2] Invalid state value: got "%s" instead of "%s"`, state, request.OAuth2State(r))
-		response.Redirect(w, r, route.Path(c.router, "login"))
+		html.Redirect(w, r, route.Path(c.router, "login"))
 		return
 	}
 
 	authProvider, err := getOAuth2Manager(c.cfg).Provider(provider)
 	if err != nil {
 		logger.Error("[OAuth2] %v", err)
-		response.Redirect(w, r, route.Path(c.router, "login"))
+		html.Redirect(w, r, route.Path(c.router, "login"))
 		return
 	}
 
 	profile, err := authProvider.GetProfile(code)
 	if err != nil {
 		logger.Error("[OAuth2] %v", err)
-		response.Redirect(w, r, route.Path(c.router, "login"))
+		html.Redirect(w, r, route.Path(c.router, "login"))
 		return
 	}
 
 	if request.IsAuthenticated(r) {
 		user, err := c.store.UserByExtraField(profile.Key, profile.ID)
 		if err != nil {
-			html.ServerError(w, err)
+			html.ServerError(w, r, err)
 			return
 		}
 
 		if user != nil {
 			logger.Error("[OAuth2] User #%d cannot be associated because %s is already associated", request.UserID(r), user.Username)
 			sess.NewFlashErrorMessage(printer.Printf("error.duplicate_linked_account"))
-			response.Redirect(w, r, route.Path(c.router, "settings"))
+			html.Redirect(w, r, route.Path(c.router, "settings"))
 			return
 		}
 
 		if err := c.store.UpdateExtraField(request.UserID(r), profile.Key, profile.ID); err != nil {
-			html.ServerError(w, err)
+			html.ServerError(w, r, err)
 			return
 		}
 
 		sess.NewFlashMessage(printer.Printf("alert.account_linked"))
-		response.Redirect(w, r, route.Path(c.router, "settings"))
+		html.Redirect(w, r, route.Path(c.router, "settings"))
 		return
 	}
 
 	user, err := c.store.UserByExtraField(profile.Key, profile.ID)
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	if user == nil {
 		if !c.cfg.IsOAuth2UserCreationAllowed() {
-			html.Forbidden(w)
+			html.Forbidden(w, r)
 			return
 		}
 
@@ -100,14 +99,14 @@ func (c *Controller) OAuth2Callback(w http.ResponseWriter, r *http.Request) {
 		user.Extra[profile.Key] = profile.ID
 
 		if err := c.store.CreateUser(user); err != nil {
-			html.ServerError(w, err)
+			html.ServerError(w, r, err)
 			return
 		}
 	}
 
 	sessionToken, _, err := c.store.CreateUserSession(user.Username, r.UserAgent(), request.ClientIP(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
@@ -123,5 +122,5 @@ func (c *Controller) OAuth2Callback(w http.ResponseWriter, r *http.Request) {
 		c.cfg.BasePath(),
 	))
 
-	response.Redirect(w, r, route.Path(c.router, "unread"))
+	html.Redirect(w, r, route.Path(c.router, "unread"))
 }
diff --git a/ui/oauth2_redirect.go b/ui/oauth2_redirect.go
index 90c20b14..e54309ab 100644
--- a/ui/oauth2_redirect.go
+++ b/ui/oauth2_redirect.go
@@ -2,13 +2,13 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package ui  // import "miniflux.app/ui"
+package ui // import "miniflux.app/ui"
 
 import (
 	"net/http"
 
 	"miniflux.app/http/request"
-	"miniflux.app/http/response"
+	"miniflux.app/http/response/html"
 	"miniflux.app/http/route"
 	"miniflux.app/logger"
 	"miniflux.app/ui/session"
@@ -21,16 +21,16 @@ func (c *Controller) OAuth2Redirect(w http.ResponseWriter, r *http.Request) {
 	provider := request.RouteStringParam(r, "provider")
 	if provider == "" {
 		logger.Error("[OAuth2] Invalid or missing provider: %s", provider)
-		response.Redirect(w, r, route.Path(c.router, "login"))
+		html.Redirect(w, r, route.Path(c.router, "login"))
 		return
 	}
 
 	authProvider, err := getOAuth2Manager(c.cfg).Provider(provider)
 	if err != nil {
 		logger.Error("[OAuth2] %v", err)
-		response.Redirect(w, r, route.Path(c.router, "login"))
+		html.Redirect(w, r, route.Path(c.router, "login"))
 		return
 	}
 
-	response.Redirect(w, r, authProvider.GetRedirectURL(sess.NewOAuth2State()))
+	html.Redirect(w, r, authProvider.GetRedirectURL(sess.NewOAuth2State()))
 }
diff --git a/ui/oauth2_unlink.go b/ui/oauth2_unlink.go
index 022e282b..8e38ddc6 100644
--- a/ui/oauth2_unlink.go
+++ b/ui/oauth2_unlink.go
@@ -2,13 +2,12 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package ui  // import "miniflux.app/ui"
+package ui // import "miniflux.app/ui"
 
 import (
 	"net/http"
 
 	"miniflux.app/http/request"
-	"miniflux.app/http/response"
 	"miniflux.app/http/response/html"
 	"miniflux.app/http/route"
 	"miniflux.app/locale"
@@ -22,14 +21,14 @@ func (c *Controller) OAuth2Unlink(w http.ResponseWriter, r *http.Request) {
 	provider := request.RouteStringParam(r, "provider")
 	if provider == "" {
 		logger.Info("[OAuth2] Invalid or missing provider")
-		response.Redirect(w, r, route.Path(c.router, "login"))
+		html.Redirect(w, r, route.Path(c.router, "login"))
 		return
 	}
 
 	authProvider, err := getOAuth2Manager(c.cfg).Provider(provider)
 	if err != nil {
 		logger.Error("[OAuth2] %v", err)
-		response.Redirect(w, r, route.Path(c.router, "settings"))
+		html.Redirect(w, r, route.Path(c.router, "settings"))
 		return
 	}
 
@@ -37,21 +36,21 @@ func (c *Controller) OAuth2Unlink(w http.ResponseWriter, r *http.Request) {
 
 	hasPassword, err := c.store.HasPassword(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	if !hasPassword {
 		sess.NewFlashErrorMessage(printer.Printf("error.unlink_account_without_password"))
-		response.Redirect(w, r, route.Path(c.router, "settings"))
+		html.Redirect(w, r, route.Path(c.router, "settings"))
 		return
 	}
 
 	if err := c.store.RemoveExtraField(request.UserID(r), authProvider.GetUserExtraKey()); err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	sess.NewFlashMessage(printer.Printf("alert.account_unlinked"))
-	response.Redirect(w, r, route.Path(c.router, "settings"))
+	html.Redirect(w, r, route.Path(c.router, "settings"))
 }
diff --git a/ui/opml_export.go b/ui/opml_export.go
index 20194b0c..55ceb561 100644
--- a/ui/opml_export.go
+++ b/ui/opml_export.go
@@ -17,9 +17,9 @@ import (
 func (c *Controller) Export(w http.ResponseWriter, r *http.Request) {
 	opml, err := opml.NewHandler(c.store).Export(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
-	xml.Attachment(w, "feeds.opml", opml)
+	xml.Attachment(w, r, "feeds.opml", opml)
 }
diff --git a/ui/opml_import.go b/ui/opml_import.go
index a8e732ed..9a763cae 100644
--- a/ui/opml_import.go
+++ b/ui/opml_import.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package ui  // import "miniflux.app/ui"
+package ui // import "miniflux.app/ui"
 
 import (
 	"net/http"
@@ -17,7 +17,7 @@ import (
 func (c *Controller) Import(w http.ResponseWriter, r *http.Request) {
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
diff --git a/ui/opml_upload.go b/ui/opml_upload.go
index 603b6600..538f8226 100644
--- a/ui/opml_upload.go
+++ b/ui/opml_upload.go
@@ -2,13 +2,12 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package ui  // import "miniflux.app/ui"
+package ui // import "miniflux.app/ui"
 
 import (
 	"net/http"
 
 	"miniflux.app/http/request"
-	"miniflux.app/http/response"
 	"miniflux.app/http/response/html"
 	"miniflux.app/http/route"
 	"miniflux.app/logger"
@@ -21,14 +20,14 @@ import (
 func (c *Controller) UploadOPML(w http.ResponseWriter, r *http.Request) {
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	file, fileHeader, err := r.FormFile("file")
 	if err != nil {
 		logger.Error("[Controller:UploadOPML] %v", err)
-		response.Redirect(w, r, route.Path(c.router, "import"))
+		html.Redirect(w, r, route.Path(c.router, "import"))
 		return
 	}
 	defer file.Close()
@@ -59,5 +58,5 @@ func (c *Controller) UploadOPML(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	response.Redirect(w, r, route.Path(c.router, "feeds"))
+	html.Redirect(w, r, route.Path(c.router, "feeds"))
 }
diff --git a/ui/proxy.go b/ui/proxy.go
index 553dcce7..050c0708 100644
--- a/ui/proxy.go
+++ b/ui/proxy.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package ui  // import "miniflux.app/ui"
+package ui // import "miniflux.app/ui"
 
 import (
 	"encoding/base64"
@@ -16,44 +16,47 @@ import (
 	"miniflux.app/http/request"
 	"miniflux.app/http/response"
 	"miniflux.app/http/response/html"
-	"miniflux.app/logger"
 )
 
 // ImageProxy fetch an image from a remote server and sent it back to the browser.
 func (c *Controller) ImageProxy(w http.ResponseWriter, r *http.Request) {
-	// If we receive a "If-None-Match" header we assume the image in stored in browser cache
+	// If we receive a "If-None-Match" header, we assume the image is already stored in browser cache.
 	if r.Header.Get("If-None-Match") != "" {
-		response.NotModified(w)
+		w.WriteHeader(http.StatusNotModified)
 		return
 	}
 
 	encodedURL := request.RouteStringParam(r, "encodedURL")
 	if encodedURL == "" {
-		html.BadRequest(w, errors.New("No URL provided"))
+		html.BadRequest(w, r, errors.New("No URL provided"))
 		return
 	}
 
 	decodedURL, err := base64.URLEncoding.DecodeString(encodedURL)
 	if err != nil {
-		html.BadRequest(w, errors.New("Unable to decode this URL"))
+		html.BadRequest(w, r, errors.New("Unable to decode this URL"))
 		return
 	}
 
 	clt := client.New(string(decodedURL))
 	resp, err := clt.Get()
 	if err != nil {
-		logger.Error("[Controller:ImageProxy] %v", err)
-		html.NotFound(w)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	if resp.HasServerFailure() {
-		html.NotFound(w)
+		html.NotFound(w, r)
 		return
 	}
 
 	body, _ := ioutil.ReadAll(resp.Body)
 	etag := crypto.HashFromBytes(body)
 
-	response.Cache(w, r, resp.ContentType, etag, body, 72*time.Hour)
+	response.New(w ,r).WithCaching(etag, 72*time.Hour, func(b *response.Builder) {
+		b.WithHeader("Content-Type", resp.ContentType)
+		b.WithBody(body)
+		b.WithoutCompression()
+		b.Write()
+	})
 }
diff --git a/ui/search_entries.go b/ui/search_entries.go
index e1dcbad2..1304ef20 100644
--- a/ui/search_entries.go
+++ b/ui/search_entries.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package ui  // import "miniflux.app/ui"
+package ui // import "miniflux.app/ui"
 
 import (
 	"net/http"
@@ -19,7 +19,7 @@ import (
 func (c *Controller) ShowSearchEntries(w http.ResponseWriter, r *http.Request) {
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
@@ -35,13 +35,13 @@ func (c *Controller) ShowSearchEntries(w http.ResponseWriter, r *http.Request) {
 
 	entries, err := builder.GetEntries()
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	count, err := builder.CountEntries()
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
diff --git a/ui/session_list.go b/ui/session_list.go
index 3f5bf9e0..76e693a1 100644
--- a/ui/session_list.go
+++ b/ui/session_list.go
@@ -2,15 +2,15 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package ui  // import "miniflux.app/ui"
+package ui // import "miniflux.app/ui"
 
 import (
 	"net/http"
 
 	"miniflux.app/http/request"
+	"miniflux.app/http/response/html"
 	"miniflux.app/ui/session"
 	"miniflux.app/ui/view"
-	"miniflux.app/http/response/html"
 )
 
 // ShowSessions shows the list of active user sessions.
@@ -20,13 +20,13 @@ func (c *Controller) ShowSessions(w http.ResponseWriter, r *http.Request) {
 
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	sessions, err := c.store.UserSessions(user.ID)
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
diff --git a/ui/session_remove.go b/ui/session_remove.go
index 27201fd5..cc626126 100644
--- a/ui/session_remove.go
+++ b/ui/session_remove.go
@@ -8,7 +8,7 @@ import (
 	"net/http"
 
 	"miniflux.app/http/request"
-	"miniflux.app/http/response"
+	"miniflux.app/http/response/html"
 	"miniflux.app/http/route"
 	"miniflux.app/logger"
 )
@@ -21,5 +21,5 @@ func (c *Controller) RemoveSession(w http.ResponseWriter, r *http.Request) {
 		logger.Error("[Controller:RemoveSession] %v", err)
 	}
 
-	response.Redirect(w, r, route.Path(c.router, "sessions"))
+	html.Redirect(w, r, route.Path(c.router, "sessions"))
 }
diff --git a/ui/settings_show.go b/ui/settings_show.go
index 07703eb1..4f4756dc 100644
--- a/ui/settings_show.go
+++ b/ui/settings_show.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package ui  // import "miniflux.app/ui"
+package ui // import "miniflux.app/ui"
 
 import (
 	"net/http"
@@ -23,7 +23,7 @@ func (c *Controller) ShowSettings(w http.ResponseWriter, r *http.Request) {
 
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
@@ -37,7 +37,7 @@ func (c *Controller) ShowSettings(w http.ResponseWriter, r *http.Request) {
 
 	timezones, err := c.store.Timezones()
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
diff --git a/ui/settings_update.go b/ui/settings_update.go
index ed424e3f..466bb782 100644
--- a/ui/settings_update.go
+++ b/ui/settings_update.go
@@ -2,12 +2,11 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package ui  // import "miniflux.app/ui"
+package ui // import "miniflux.app/ui"
 
 import (
 	"net/http"
 
-	"miniflux.app/http/response"
 	"miniflux.app/http/response/html"
 	"miniflux.app/http/request"
 	"miniflux.app/http/route"
@@ -26,13 +25,13 @@ func (c *Controller) UpdateSettings(w http.ResponseWriter, r *http.Request) {
 
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	timezones, err := c.store.Timezones()
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
@@ -70,5 +69,5 @@ func (c *Controller) UpdateSettings(w http.ResponseWriter, r *http.Request) {
 	sess.SetLanguage(user.Language)
 	sess.SetTheme(user.Theme)
 	sess.NewFlashMessage(locale.NewPrinter(request.UserLanguage(r)).Printf("alert.prefs_saved"))
-	response.Redirect(w, r, route.Path(c.router, "settings"))
+	html.Redirect(w, r, route.Path(c.router, "settings"))
 }
diff --git a/ui/static_app_icon.go b/ui/static_app_icon.go
index 2ad42fd8..9e2a448d 100644
--- a/ui/static_app_icon.go
+++ b/ui/static_app_icon.go
@@ -12,26 +12,28 @@ import (
 	"miniflux.app/http/request"
 	"miniflux.app/http/response"
 	"miniflux.app/http/response/html"
-	"miniflux.app/logger"
 	"miniflux.app/ui/static"
 )
 
-// AppIcon renders application icons.
+// AppIcon shows application icons.
 func (c *Controller) AppIcon(w http.ResponseWriter, r *http.Request) {
 	filename := request.RouteStringParam(r, "filename")
-	encodedBlob, found := static.Binaries[filename]
+	etag, found := static.BinariesChecksums[filename]
 	if !found {
-		logger.Info("[Controller:AppIcon] This icon doesn't exists: %s", filename)
-		html.NotFound(w)
+		html.NotFound(w, r)
 		return
 	}
 
-	blob, err := base64.StdEncoding.DecodeString(encodedBlob)
-	if err != nil {
-		logger.Error("[Controller:AppIcon] %v", err)
-		html.NotFound(w)
-		return
-	}
+	response.New(w, r).WithCaching(etag, 72*time.Hour, func(b *response.Builder) {
+		blob, err := base64.StdEncoding.DecodeString(static.Binaries[filename])
+		if err != nil {
+			html.ServerError(w, r, err)
+			return
+		}
 
-	response.Cache(w, r, "image/png", static.BinariesChecksums[filename], blob, 48*time.Hour)
+		b.WithHeader("Content-Type", "image/png")
+		b.WithoutCompression()
+		b.WithBody(blob)
+		b.Write()
+	})
 }
diff --git a/ui/static_favicon.go b/ui/static_favicon.go
index 2e19c055..1266a199 100644
--- a/ui/static_favicon.go
+++ b/ui/static_favicon.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package ui  // import "miniflux.app/ui"
+package ui // import "miniflux.app/ui"
 
 import (
 	"encoding/base64"
@@ -11,18 +11,27 @@ import (
 
 	"miniflux.app/http/response"
 	"miniflux.app/http/response/html"
-	"miniflux.app/logger"
 	"miniflux.app/ui/static"
 )
 
-// Favicon renders the application favicon.
+// Favicon shows the application favicon.
 func (c *Controller) Favicon(w http.ResponseWriter, r *http.Request) {
-	blob, err := base64.StdEncoding.DecodeString(static.Binaries["favicon.ico"])
-	if err != nil {
-		logger.Error("[Controller:Favicon] %v", err)
-		html.NotFound(w)
+	etag, found := static.BinariesChecksums["favicon.ico"]
+	if !found {
+		html.NotFound(w, r)
 		return
 	}
 
-	response.Cache(w, r, "image/x-icon", static.BinariesChecksums["favicon.ico"], blob, 48*time.Hour)
+	response.New(w, r).WithCaching(etag, 48*time.Hour, func(b *response.Builder) {
+		blob, err := base64.StdEncoding.DecodeString(static.Binaries["favicon.ico"])
+		if err != nil {
+			html.ServerError(w, r, err)
+			return
+		}
+
+		b.WithHeader("Content-Type", "image/x-icon")
+		b.WithoutCompression()
+		b.WithBody(blob)
+		b.Write()
+	})
 }
diff --git a/ui/static_javascript.go b/ui/static_javascript.go
index 248fae3a..ff7fd16b 100644
--- a/ui/static_javascript.go
+++ b/ui/static_javascript.go
@@ -17,13 +17,15 @@ import (
 // Javascript renders application client side code.
 func (c *Controller) Javascript(w http.ResponseWriter, r *http.Request) {
 	filename := request.RouteStringParam(r, "name")
-	if _, found := static.Javascripts[filename]; !found {
-		html.NotFound(w)
+	etag, found := static.JavascriptsChecksums[filename]
+	if !found {
+		html.NotFound(w, r)
 		return
 	}
 
-	body := static.Javascripts[filename]
-	etag := static.JavascriptsChecksums[filename]
-
-	response.Cache(w, r, "text/javascript; charset=utf-8", etag, []byte(body), 48*time.Hour)
+	response.New(w, r).WithCaching(etag, 48*time.Hour, func(b *response.Builder) {
+		b.WithHeader("Content-Type", "text/javascript; charset=utf-8")
+		b.WithBody(static.Javascripts[filename])
+		b.Write()
+	})
 }
diff --git a/ui/static_stylesheet.go b/ui/static_stylesheet.go
index 8e475bdc..9a21bc61 100644
--- a/ui/static_stylesheet.go
+++ b/ui/static_stylesheet.go
@@ -16,14 +16,16 @@ import (
 
 // Stylesheet renders the CSS.
 func (c *Controller) Stylesheet(w http.ResponseWriter, r *http.Request) {
-	stylesheet := request.RouteStringParam(r, "name")
-	if _, found := static.Stylesheets[stylesheet]; !found {
-		html.NotFound(w)
+	filename := request.RouteStringParam(r, "name")
+	etag, found := static.StylesheetsChecksums[filename]
+	if !found {
+		html.NotFound(w, r)
 		return
 	}
 
-	body := static.Stylesheets[stylesheet]
-	etag := static.StylesheetsChecksums[stylesheet]
-
-	response.Cache(w, r, "text/css; charset=utf-8", etag, []byte(body), 48*time.Hour)
+	response.New(w, r).WithCaching(etag, 48*time.Hour, func(b *response.Builder) {
+		b.WithHeader("Content-Type", "text/css; charset=utf-8")
+		b.WithBody(static.Stylesheets[filename])
+		b.Write()
+	})
 }
diff --git a/ui/subscription_add.go b/ui/subscription_add.go
index 387f3405..3056eec9 100644
--- a/ui/subscription_add.go
+++ b/ui/subscription_add.go
@@ -21,13 +21,13 @@ func (c *Controller) AddSubscription(w http.ResponseWriter, r *http.Request) {
 
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	categories, err := c.store.Categories(user.ID)
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
diff --git a/ui/subscription_bookmarklet.go b/ui/subscription_bookmarklet.go
index cba039a2..5c0e08c0 100644
--- a/ui/subscription_bookmarklet.go
+++ b/ui/subscription_bookmarklet.go
@@ -22,13 +22,13 @@ func (c *Controller) Bookmarklet(w http.ResponseWriter, r *http.Request) {
 
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	categories, err := c.store.Categories(user.ID)
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
diff --git a/ui/subscription_choose.go b/ui/subscription_choose.go
index 10e46ad7..4f701c89 100644
--- a/ui/subscription_choose.go
+++ b/ui/subscription_choose.go
@@ -2,13 +2,12 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package ui  // import "miniflux.app/ui"
+package ui // import "miniflux.app/ui"
 
 import (
 	"net/http"
 
 	"miniflux.app/http/client"
-	"miniflux.app/http/response"
 	"miniflux.app/http/response/html"
 	"miniflux.app/http/request"
 	"miniflux.app/http/route"
@@ -24,13 +23,13 @@ func (c *Controller) ChooseSubscription(w http.ResponseWriter, r *http.Request)
 
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	categories, err := c.store.Categories(user.ID)
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
@@ -65,5 +64,5 @@ func (c *Controller) ChooseSubscription(w http.ResponseWriter, r *http.Request)
 		return
 	}
 
-	response.Redirect(w, r, route.Path(c.router, "feedEntries", "feedID", feed.ID))
+	html.Redirect(w, r, route.Path(c.router, "feedEntries", "feedID", feed.ID))
 }
diff --git a/ui/subscription_submit.go b/ui/subscription_submit.go
index d1cefbd0..801e5167 100644
--- a/ui/subscription_submit.go
+++ b/ui/subscription_submit.go
@@ -8,7 +8,6 @@ import (
 	"net/http"
 
 	"miniflux.app/http/client"
-	"miniflux.app/http/response"
 	"miniflux.app/http/response/html"
 	"miniflux.app/http/request"
 	"miniflux.app/http/route"
@@ -26,13 +25,13 @@ func (c *Controller) SubmitSubscription(w http.ResponseWriter, r *http.Request)
 
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	categories, err := c.store.Categories(user.ID)
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
@@ -90,7 +89,7 @@ func (c *Controller) SubmitSubscription(w http.ResponseWriter, r *http.Request)
 			return
 		}
 
-		response.Redirect(w, r, route.Path(c.router, "feedEntries", "feedID", feed.ID))
+		html.Redirect(w, r, route.Path(c.router, "feedEntries", "feedID", feed.ID))
 	case n > 1:
 		v := view.New(c.tpl, r, sess)
 		v.Set("subscriptions", subscriptions)
diff --git a/ui/unread_entries.go b/ui/unread_entries.go
index 5992162d..cb024282 100644
--- a/ui/unread_entries.go
+++ b/ui/unread_entries.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package ui  // import "miniflux.app/ui"
+package ui // import "miniflux.app/ui"
 
 import (
 	"net/http"
@@ -22,7 +22,7 @@ func (c *Controller) ShowUnreadPage(w http.ResponseWriter, r *http.Request) {
 
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
@@ -31,7 +31,7 @@ func (c *Controller) ShowUnreadPage(w http.ResponseWriter, r *http.Request) {
 	builder.WithStatus(model.EntryStatusUnread)
 	countUnread, err := builder.CountEntries()
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
@@ -47,7 +47,7 @@ func (c *Controller) ShowUnreadPage(w http.ResponseWriter, r *http.Request) {
 	builder.WithLimit(nbItemsPerPage)
 	entries, err := builder.GetEntries()
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
diff --git a/ui/unread_mark_all_read.go b/ui/unread_mark_all_read.go
index c4679235..724da67e 100644
--- a/ui/unread_mark_all_read.go
+++ b/ui/unread_mark_all_read.go
@@ -8,7 +8,7 @@ import (
 	"net/http"
 
 	"miniflux.app/http/request"
-	"miniflux.app/http/response"
+	"miniflux.app/http/response/html"
 	"miniflux.app/http/route"
 	"miniflux.app/logger"
 )
@@ -19,5 +19,5 @@ func (c *Controller) MarkAllAsRead(w http.ResponseWriter, r *http.Request) {
 		logger.Error("[MarkAllAsRead] %v", err)
 	}
 
-	response.Redirect(w, r, route.Path(c.router, "unread"))
+	html.Redirect(w, r, route.Path(c.router, "unread"))
 }
diff --git a/ui/user_create.go b/ui/user_create.go
index b31b0de2..a94d286a 100644
--- a/ui/user_create.go
+++ b/ui/user_create.go
@@ -21,12 +21,12 @@ func (c *Controller) CreateUser(w http.ResponseWriter, r *http.Request) {
 
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	if !user.IsAdmin {
-		html.Forbidden(w)
+		html.Forbidden(w, r)
 		return
 	}
 
diff --git a/ui/user_edit.go b/ui/user_edit.go
index f2c1abfd..2348a7dd 100644
--- a/ui/user_edit.go
+++ b/ui/user_edit.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package ui  // import "miniflux.app/ui"
+package ui // import "miniflux.app/ui"
 
 import (
 	"net/http"
@@ -21,24 +21,24 @@ func (c *Controller) EditUser(w http.ResponseWriter, r *http.Request) {
 
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	if !user.IsAdmin {
-		html.Forbidden(w)
+		html.Forbidden(w, r)
 		return
 	}
 
 	userID := request.RouteInt64Param(r, "userID")
 	selectedUser, err := c.store.UserByID(userID)
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	if selectedUser == nil {
-		html.NotFound(w)
+		html.NotFound(w, r)
 		return
 	}
 
diff --git a/ui/user_list.go b/ui/user_list.go
index dce30e0d..3c9e3edd 100644
--- a/ui/user_list.go
+++ b/ui/user_list.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package ui  // import "miniflux.app/ui"
+package ui // import "miniflux.app/ui"
 
 import (
 	"net/http"
@@ -20,18 +20,18 @@ func (c *Controller) ShowUsers(w http.ResponseWriter, r *http.Request) {
 
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	if !user.IsAdmin {
-		html.Forbidden(w)
+		html.Forbidden(w, r)
 		return
 	}
 
 	users, err := c.store.Users()
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
diff --git a/ui/user_remove.go b/ui/user_remove.go
index 981a7a25..beda7be9 100644
--- a/ui/user_remove.go
+++ b/ui/user_remove.go
@@ -8,7 +8,6 @@ import (
 	"net/http"
 
 	"miniflux.app/http/request"
-	"miniflux.app/http/response"
 	"miniflux.app/http/response/html"
 	"miniflux.app/http/route"
 )
@@ -17,31 +16,31 @@ import (
 func (c *Controller) RemoveUser(w http.ResponseWriter, r *http.Request) {
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	if !user.IsAdmin {
-		html.Forbidden(w)
+		html.Forbidden(w, r)
 		return
 	}
 
 	userID := request.RouteInt64Param(r, "userID")
 	selectedUser, err := c.store.UserByID(userID)
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	if selectedUser == nil {
-		html.NotFound(w)
+		html.NotFound(w, r)
 		return
 	}
 
 	if err := c.store.RemoveUser(selectedUser.ID); err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
-	response.Redirect(w, r, route.Path(c.router, "users"))
+	html.Redirect(w, r, route.Path(c.router, "users"))
 }
diff --git a/ui/user_save.go b/ui/user_save.go
index 7c3685b5..a4be3465 100644
--- a/ui/user_save.go
+++ b/ui/user_save.go
@@ -7,7 +7,6 @@ package ui  // import "miniflux.app/ui"
 import (
 	"net/http"
 
-	"miniflux.app/http/response"
 	"miniflux.app/http/response/html"
 	"miniflux.app/http/request"
 	"miniflux.app/http/route"
@@ -21,12 +20,12 @@ import (
 func (c *Controller) SaveUser(w http.ResponseWriter, r *http.Request) {
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	if !user.IsAdmin {
-		html.Forbidden(w)
+		html.Forbidden(w, r)
 		return
 	}
 
@@ -60,5 +59,5 @@ func (c *Controller) SaveUser(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	response.Redirect(w, r, route.Path(c.router, "users"))
+	html.Redirect(w, r, route.Path(c.router, "users"))
 }
diff --git a/ui/user_update.go b/ui/user_update.go
index 006d49a6..34567e03 100644
--- a/ui/user_update.go
+++ b/ui/user_update.go
@@ -2,13 +2,12 @@
 // Use of this source code is governed by the Apache 2.0
 // license that can be found in the LICENSE file.
 
-package ui  // import "miniflux.app/ui"
+package ui // import "miniflux.app/ui"
 
 import (
 	"net/http"
 
 	"miniflux.app/http/request"
-	"miniflux.app/http/response"
 	"miniflux.app/http/response/html"
 	"miniflux.app/http/route"
 	"miniflux.app/logger"
@@ -21,24 +20,24 @@ import (
 func (c *Controller) UpdateUser(w http.ResponseWriter, r *http.Request) {
 	user, err := c.store.UserByID(request.UserID(r))
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	if !user.IsAdmin {
-		html.Forbidden(w)
+		html.Forbidden(w, r)
 		return
 	}
 
 	userID := request.RouteInt64Param(r, "userID")
 	selectedUser, err := c.store.UserByID(userID)
 	if err != nil {
-		html.ServerError(w, err)
+		html.ServerError(w, r, err)
 		return
 	}
 
 	if selectedUser == nil {
-		html.NotFound(w)
+		html.NotFound(w, r)
 		return
 	}
 
@@ -73,5 +72,5 @@ func (c *Controller) UpdateUser(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	response.Redirect(w, r, route.Path(c.router, "users"))
+	html.Redirect(w, r, route.Path(c.router, "users"))
 }