diff --git a/.gitignore b/.gitignore
index 056a8b8333d18cc7e1491ae9f0347278b9fe9f3d..87b17afd9840960d771773c6f30d38f2544eb80f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,6 +34,9 @@ spec/fixtures/*.y*ml
 spec/fixtures/*.fixture.*
 coverage/
 xml_locales/
+public/404.html
+public/422.html
+public/500.html
 
 # Sprites
 app/assets/images/branding-*.png
diff --git a/public/peeping-tom.png b/app/assets/images/peeping-tom.png
similarity index 100%
rename from public/peeping-tom.png
rename to app/assets/images/peeping-tom.png
diff --git a/app/assets/stylesheets/error_pages.scss b/app/assets/stylesheets/error_pages.scss
index a60f66e436be444793078ae0d9daf581a8b11287..452352e65acbcf8291b0c683d52f6d6d61d94f4e 100644
--- a/app/assets/stylesheets/error_pages.scss
+++ b/app/assets/stylesheets/error_pages.scss
@@ -23,3 +23,85 @@
   position: absolute;
   left: 0; right: 0;
 }
+
+#error_404 {
+  width: 100%;
+  height: 100%;
+  position: fixed;
+  bottom:0px;
+  margin: 0px;
+  font-family: Roboto, Helvetica, Arial, sans-serif;
+  text-align: center;
+  text-shadow: 0 1px 0 #fff;
+  color: #666;
+  background: image-url("peeping-tom.png") no-repeat bottom;
+
+  #big-number {
+    font-family: Roboto-BoldCondensed, Helvetica, Arial, sans-serif;
+    font-size: 250px;
+    text-shadow: 0 2px 0 #fff, 0 -1px 0 #999;
+    color: #ddd;
+  }
+
+  a {
+    text-decoration : none;
+    color : rgb(42,156,235);
+  }
+
+  a:hover {
+    text-decoration : underline;
+  }
+
+  .transparent {
+    filter: alpha(opacity=80);
+    opacity: 0.8;
+  }
+}
+
+#error_422 {
+  background-color: #fff;
+  color: #666;
+  text-align: center;
+  font-family: arial, sans-serif;
+
+  div.dialog {
+    width: 25em;
+    padding: 0 4em;
+    margin: 4em auto 0 auto;
+    border: 1px solid #ccc;
+    border-right-color: #999;
+    border-bottom-color: #999;
+  }
+
+  h1 {
+    font-size: 100%;
+    color: #f00;
+    line-height: 1.5em;
+  }
+}
+
+#error_500 {
+  text-align: center;
+  background-color: rgb(252,252,252);
+  color: #444;
+  font-family: 'helvetica neue', 'helvetica', 'arial', sans-serif;
+  margin: 0;
+  padding: 1em;
+
+  header {
+    height: 100px;
+    background-color: #333;
+    position:relative;
+  }
+
+  #diaspora_logo {
+    position: relative;
+    margin-top: 50px;
+  }
+
+  h1 {
+    font-size: 100%;
+    color: #444;
+    line-height: 1.5em;
+  }
+}
diff --git a/app/views/errors/error_404.haml b/app/views/errors/error_404.haml
new file mode 100644
index 0000000000000000000000000000000000000000..e66451207f8fa9976ac8f4346c5f179e65edb660
--- /dev/null
+++ b/app/views/errors/error_404.haml
@@ -0,0 +1,10 @@
+- content_for(:page_title) do
+  The page you were looking for doesn't exist (404)
+
+#big-number.transparent
+  404
+%p
+  These are not the kittens you're looking for. Move along.
+%p
+  %a{href: "javascript:history.back()"}
+    Go Back?
diff --git a/app/views/errors/error_422.haml b/app/views/errors/error_422.haml
new file mode 100644
index 0000000000000000000000000000000000000000..f602b05d0a92f5f7ab18a4dc82b510de49a5ede4
--- /dev/null
+++ b/app/views/errors/error_422.haml
@@ -0,0 +1,8 @@
+- content_for(:page_title) do
+  The change you wanted was rejected (422)
+
+.dialog
+  %h1
+    The change you wanted was rejected.
+  %p
+    Maybe you tried to change something you didn't have access to.
diff --git a/app/views/errors/error_500.haml b/app/views/errors/error_500.haml
new file mode 100644
index 0000000000000000000000000000000000000000..c7bee9257407fe886c3f9093dedb36fecacde70c
--- /dev/null
+++ b/app/views/errors/error_500.haml
@@ -0,0 +1,10 @@
+- content_for(:page_title) do
+  We're sorry, but something went wrong (500)
+
+%header
+  = image_tag "branding/white2x.png", id: "diaspora_logo"
+
+%h1
+  500: Internal server error.
+%h3
+  Our bad! Sorry about that. :(
diff --git a/app/views/layouts/application.mobile.haml b/app/views/layouts/application.mobile.haml
index 3c4dbe5017f5973e82d60a6ee6fde6273d64b5d3..4ae09e5b6bbcef7b1f71a0e7572e6794c1473951 100644
--- a/app/views/layouts/application.mobile.haml
+++ b/app/views/layouts/application.mobile.haml
@@ -20,9 +20,9 @@
     %meta{'http-equiv' => "cleartype", :content => 'on'}/
 
     / Home screen icon (sized for retina displays)
-    %link{:rel => 'apple-touch-icon', :href => '/apple-touch-icon.png'}
+    %link{rel: "apple-touch-icon", href: image_path("apple-touch-icon.png")}
     / For Nokia devices
-    %link{:rel => 'shortcut icon', :href => '/apple-touch-icon.png'}
+    %link{rel: "shortcut icon", href: image_path("apple-touch-icon.png")}
 
     / iOS mobile web app indicator
     / NOTE(we will enable these once we don't have to rely on back/forward buttons anymore)
diff --git a/app/views/layouts/error_page.haml b/app/views/layouts/error_page.haml
new file mode 100644
index 0000000000000000000000000000000000000000..5f7fee3d20d38de4c00aa19ce6ab216a88328822
--- /dev/null
+++ b/app/views/layouts/error_page.haml
@@ -0,0 +1,14 @@
+!!!
+%html
+  %head
+    %title= page_title yield(:page_title)
+
+    %link{rel: "shortcut icon", href: image_path("favicon.png")}
+    %link{rel: "apple-touch-icon", href: image_path("apple-touch-icon.png")}
+
+    = stylesheet_link_tag :error_pages, media: "all"
+
+    = yield(:head)
+
+  %body{id: "error_#{@code}"}
+    = yield
diff --git a/lib/tasks/assets.rake b/lib/tasks/assets.rake
new file mode 100644
index 0000000000000000000000000000000000000000..cc84fbf974f88bcc4b577c00af4d0487945fac32
--- /dev/null
+++ b/lib/tasks/assets.rake
@@ -0,0 +1,59 @@
+# Inspired by https://github.com/route/errgent/blob/master/lib/errgent/renderer.rb
+class ErrorPageRenderer
+  def initialize options={}
+    @codes    = options.fetch :codes, [404, 500]
+    @output   = options.fetch :output, "public/%s.html"
+    @vars     = options.fetch :vars, {}
+    @template = options.fetch :template, "errors/error_%s"
+    @layout   = options.fetch :layout, "layouts/error_page"
+  end
+
+  def render
+    @codes.each do |code|
+      view = build_action_view
+      view.assign @vars.merge(code: code)
+      path = Rails.root.join(@output % code)
+      File.write path, view.render(template: @template % code, layout: @layout)
+    end
+  end
+
+  def helpers(&block)
+    @helpers = block
+  end
+
+  private
+
+  def build_action_view
+    paths = ::ActionController::Base.view_paths
+    ::ActionView::Base.new(paths).tap do |view|
+      view.class_eval do
+        include Rails.application.helpers
+        include Rails.application.routes.url_helpers
+      end
+      view.assets_manifest = build_manifest(Rails.application)
+      view.class_eval(&@helpers) if @helpers
+    end
+  end
+
+  # Internal API from the sprocket-rails railtie, if somebody finds a way to
+  # call it, please replace it. Might need to be updated on sprocket-rails
+  # updates.
+  def build_manifest(app)
+    config = app.config
+    path = File.join(config.paths['public'].first, config.assets.prefix)
+    Sprockets::Manifest.new(app.assets, path, config.assets.manifest)
+  end
+end
+
+namespace :assets do
+  desc "Generate error pages"
+  task :generate_error_pages do
+    renderer = ErrorPageRenderer.new codes: [404, 422, 500]
+    renderer.render
+  end
+
+  # Augment precompile with error page generation
+  task :precompile do
+    Rake::Task['assets:generate_error_pages'].invoke
+  end
+end
diff --git a/lib/tasks/tests.rake b/lib/tasks/tests.rake
index 344590cfe1cdb742d6cd43d302dd4ed0da06dfe5..11c9bf3c53192f53a97fed78913c1595d378dd2e 100644
--- a/lib/tasks/tests.rake
+++ b/lib/tasks/tests.rake
@@ -1,13 +1,14 @@
 namespace :ci do
   namespace :travis do
+    task prepare_db: %w(db:create db:test:load)
+    task prepare: %w(prepare_db assets:generate_error_pages)
+
     desc "Run everyhting except cucumber"
-    task :other => [ :prepare_db, "tests:generate_fixtures", :spec, "jasmine:ci" ]
+    task other: %w(prepare tests:generate_fixtures spec jasmine:ci)
 
     desc "Run cucumber"
-    task :cucumber => [ :prepare_db, "rake:cucumber" ]
+    task cucumber: %w(prepare rake:cucumber)
 
-    desc "Prepare db"
-    task :prepare_db => [ "db:create", "db:test:load"]
   end
 end
 
diff --git a/public/404.css b/public/404.css
deleted file mode 100644
index b0d7864ad23a1e9d0ef338568f852366ccf579b9..0000000000000000000000000000000000000000
--- a/public/404.css
+++ /dev/null
@@ -1,52 +0,0 @@
-@font-face {
-	font-family:Roboto;
-	src: local("Roboto-Regular.ttf")
-}
-
-@font-face {
-	font-family:Roboto-BoldCondensed;
-	src: local("Roboto-BoldCondensed.ttf")
-}
-
-html {
-	background: url("bgpattern.png") #ebebeb;
-	text-align: center;
-}
-
-body {
-	width: 100%;
-	height: 100%;
-	position: fixed;
-	bottom:0px;
-	margin: 0px;
-	font-family: Roboto, Helvetica, Arial, sans-serif;
-	text-align: center;
-	text-shadow: 0 1px 0 #fff;
-	color: #666;
-	background: url("peeping-tom.png") no-repeat bottom;
-}
-
-#big-number {
-  font-family: Roboto-BoldCondensed, Helvetica, Arial, sans-serif;
-  font-size: 250px;
-  text-shadow: 0 2px 0 #fff, 0 -1px 0 #999;
-  color: #ddd;
-}
-
-a {
-  text-decoration : none;
-  color : rgb(42,156,235);
-}
-
-a:hover {
-  text-decoration : underline;
-}
-
-.transparent {
-  -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=80)";
-  filter: alpha(opacity=80);
-  opacity: 0.8;
-  -moz-opacity: 0.8;
-  -khtml-opacity: 0.8;
-  -webkit-opacity: 0.8;
-}
diff --git a/public/404.html b/public/404.html
deleted file mode 100644
index daa4be7f5296c6fbb60717f37b116167d6c26dac..0000000000000000000000000000000000000000
--- a/public/404.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<html>
-	<head>
-		<title>The page you were looking for doesn't exist (404)</title>
-		<link href="/favicon.ico" rel="shortcut icon">
-		<link href="/404.css" type="text/css" rel="stylesheet">
-	</head>
-	<body>
-		<!-- This file lives in public/404.html -->
-		<div id="big-number" class="transparent">404</div>
-		<p>These are not the kittens you're looking for. Move along.</p>
-		<p><a href="javascript:history.back()">Go Back?</a></p>
-	</body>
-</html>
diff --git a/public/422.html b/public/422.html
deleted file mode 100644
index 83660ab1878ba9adc6477ed910333e32bb6b46ce..0000000000000000000000000000000000000000
--- a/public/422.html
+++ /dev/null
@@ -1,26 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-  <title>The change you wanted was rejected (422)</title>
-  <style type="text/css">
-    body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
-    div.dialog {
-      width: 25em;
-      padding: 0 4em;
-      margin: 4em auto 0 auto;
-      border: 1px solid #ccc;
-      border-right-color: #999;
-      border-bottom-color: #999;
-    }
-    h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
-  </style>
-</head>
-
-<body>
-  <!-- This file lives in public/422.html -->
-  <div class="dialog">
-    <h1>The change you wanted was rejected.</h1>
-    <p>Maybe you tried to change something you didn't have access to.</p>
-  </div>
-</body>
-</html>
diff --git a/public/500.html b/public/500.html
deleted file mode 100644
index d90787b1de0c0d14e5adca5f5939e75f76d26050..0000000000000000000000000000000000000000
--- a/public/500.html
+++ /dev/null
@@ -1,47 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-  <title>We're sorry, but something went wrong (500)</title>
-  <style type="text/css">
-    body {
-			text-align: center;
-			background-color: rgb(252,252,252);
-			color: #444;
-			font-family: 'helvetica neue', 'helvetica', 'arial', sans-serif;
-			margin: 0; 
-			padding: 1em;
-		}
-
-    header {
-			height: 100px;
-			background-color: #333;
-			position:relative;
-		}
-
-		#diaspora_logo {
-			position: relative;
-			margin-top: 50px;
-		}
-
-    h1 {
-			font-size: 100%;
-			color: #444;
-			line-height: 1.5em;
-		}
-	</style>
-</head>
-
-<body>
-  <!-- This file lives in public/500.html -->
-  <header>
-		<img id="diaspora_logo" src="/assets/branding/white2x.png"/>
-  </header>
-
-	<h1>
-		500: Internal server error.
-	</h1>
-	<h3>
-		Our bad! Sorry about that. :(
-	</h3>
-</body>
-</html>
diff --git a/public/Roboto-BoldCondensed.ttf b/public/Roboto-BoldCondensed.ttf
deleted file mode 100644
index d7ea8833ba369ec975846497e5fa05741295eacf..0000000000000000000000000000000000000000
Binary files a/public/Roboto-BoldCondensed.ttf and /dev/null differ
diff --git a/public/Roboto-Regular.ttf b/public/Roboto-Regular.ttf
deleted file mode 100644
index 7d9a6c4c32d7e920b549caf531e390733496b6e0..0000000000000000000000000000000000000000
Binary files a/public/Roboto-Regular.ttf and /dev/null differ
diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png
deleted file mode 100644
index 3027448479dbbf0fe1b0a3ba868e8f6ed43ed285..0000000000000000000000000000000000000000
Binary files a/public/apple-touch-icon.png and /dev/null differ
diff --git a/public/bgpattern.png b/public/bgpattern.png
deleted file mode 100644
index a2119535685635ad0b18e2cd5d642b5d49c6be6a..0000000000000000000000000000000000000000
Binary files a/public/bgpattern.png and /dev/null differ
diff --git a/public/icon_128.gif b/public/icon_128.gif
deleted file mode 100644
index fd164228ec528c77747fcbc76f256f3590829f79..0000000000000000000000000000000000000000
Binary files a/public/icon_128.gif and /dev/null differ