diff --git a/app/Book.php b/app/Book.php
index aa2dee9c0..91f74ca64 100644
--- a/app/Book.php
+++ b/app/Book.php
@@ -13,9 +13,9 @@ class Book extends Entity
     public function getUrl($path = false)
     {
         if ($path !== false) {
-            return baseUrl('/books/' . $this->slug . '/' . trim($path, '/'));
+            return baseUrl('/books/' . urlencode($this->slug) . '/' . trim($path, '/'));
         }
-        return baseUrl('/books/' . $this->slug);
+        return baseUrl('/books/' . urlencode($this->slug));
     }
 
     /*
diff --git a/app/Chapter.php b/app/Chapter.php
index 8f0453172..cc5518b7a 100644
--- a/app/Chapter.php
+++ b/app/Chapter.php
@@ -32,9 +32,9 @@ class Chapter extends Entity
     {
         $bookSlug = $this->getAttribute('bookSlug') ? $this->getAttribute('bookSlug') : $this->book->slug;
         if ($path !== false) {
-            return baseUrl('/books/' . $bookSlug. '/chapter/' . $this->slug . '/' . trim($path, '/'));
+            return baseUrl('/books/' . urlencode($bookSlug) . '/chapter/' . urlencode($this->slug) . '/' . trim($path, '/'));
         }
-        return baseUrl('/books/' . $bookSlug. '/chapter/' . $this->slug);
+        return baseUrl('/books/' . urlencode($bookSlug) . '/chapter/' . urlencode($this->slug));
     }
 
     /**
diff --git a/app/Http/Controllers/ChapterController.php b/app/Http/Controllers/ChapterController.php
index 57ca58beb..a3fb600fd 100644
--- a/app/Http/Controllers/ChapterController.php
+++ b/app/Http/Controllers/ChapterController.php
@@ -115,8 +115,10 @@ class ChapterController extends Controller
         $book = $this->bookRepo->getBySlug($bookSlug);
         $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
         $this->checkOwnablePermission('chapter-update', $chapter);
+        if ($chapter->name !== $request->get('name')) {
+            $chapter->slug = $this->chapterRepo->findSuitableSlug($request->get('name'), $book->id, $chapter->id);
+        }
         $chapter->fill($request->all());
-        $chapter->slug = $this->chapterRepo->findSuitableSlug($chapter->name, $book->id, $chapter->id);
         $chapter->updated_by = user()->id;
         $chapter->save();
         Activity::add($chapter, 'chapter_update', $book->id);
diff --git a/app/Page.php b/app/Page.php
index 34634b351..3ee9e90f4 100644
--- a/app/Page.php
+++ b/app/Page.php
@@ -72,13 +72,13 @@ class Page extends Entity
     {
         $bookSlug = $this->getAttribute('bookSlug') ? $this->getAttribute('bookSlug') : $this->book->slug;
         $midText = $this->draft ? '/draft/' : '/page/';
-        $idComponent = $this->draft ? $this->id : $this->slug;
+        $idComponent = $this->draft ? $this->id : urlencode($this->slug);
 
         if ($path !== false) {
-            return baseUrl('/books/' . $bookSlug . $midText . $idComponent . '/' . trim($path, '/'));
+            return baseUrl('/books/' . urlencode($bookSlug) . $midText . $idComponent . '/' . trim($path, '/'));
         }
 
-        return baseUrl('/books/' . $bookSlug . $midText . $idComponent);
+        return baseUrl('/books/' . urlencode($bookSlug) . $midText . $idComponent);
     }
 
     /**
diff --git a/app/Repos/BookRepo.php b/app/Repos/BookRepo.php
index 1af02035d..7bb91f472 100644
--- a/app/Repos/BookRepo.php
+++ b/app/Repos/BookRepo.php
@@ -147,8 +147,10 @@ class BookRepo extends EntityRepo
      */
     public function updateFromInput(Book $book, $input)
     {
+        if ($book->name !== $input['name']) {
+            $book->slug = $this->findSuitableSlug($input['name'], $book->id);
+        }
         $book->fill($input);
-        $book->slug = $this->findSuitableSlug($book->name, $book->id);
         $book->updated_by = user()->id;
         $book->save();
         $this->permissionService->buildJointPermissionsForEntity($book);
@@ -208,8 +210,7 @@ class BookRepo extends EntityRepo
      */
     public function findSuitableSlug($name, $currentId = false)
     {
-        $slug = Str::slug($name);
-        if ($slug === "") $slug = substr(md5(rand(1, 500)), 0, 5);
+        $slug = $this->nameToSlug($name);
         while ($this->doesSlugExist($slug, $currentId)) {
             $slug .= '-' . substr(md5(rand(1, 500)), 0, 3);
         }
diff --git a/app/Repos/ChapterRepo.php b/app/Repos/ChapterRepo.php
index 96f5b2d1e..4c13b9aaf 100644
--- a/app/Repos/ChapterRepo.php
+++ b/app/Repos/ChapterRepo.php
@@ -150,8 +150,7 @@ class ChapterRepo extends EntityRepo
      */
     public function findSuitableSlug($name, $bookId, $currentId = false)
     {
-        $slug = Str::slug($name);
-        if ($slug === "") $slug = substr(md5(rand(1, 500)), 0, 5);
+        $slug = $this->nameToSlug($name);
         while ($this->doesSlugExist($slug, $bookId, $currentId)) {
             $slug .= '-' . substr(md5(rand(1, 500)), 0, 3);
         }
diff --git a/app/Repos/EntityRepo.php b/app/Repos/EntityRepo.php
index 42b0b6b7b..7ecfb758c 100644
--- a/app/Repos/EntityRepo.php
+++ b/app/Repos/EntityRepo.php
@@ -269,6 +269,19 @@ class EntityRepo
         $this->permissionService->buildJointPermissionsForEntities($collection);
     }
 
+    /**
+     * Format a name as a url slug.
+     * @param $name
+     * @return string
+     */
+    protected function nameToSlug($name)
+    {
+        $slug = str_replace(' ', '-', strtolower($name));
+        $slug = preg_replace('/[\+\/\\\?\@\}\{\.\,\=\[\]\#\&\!\*\'\;\:\$\%]/', '', $slug);
+        if ($slug === "") $slug = substr(md5(rand(1, 500)), 0, 5);
+        return $slug;
+    }
+
 }
 
 
diff --git a/app/Repos/PageRepo.php b/app/Repos/PageRepo.php
index 72a310ad2..e6d713f77 100644
--- a/app/Repos/PageRepo.php
+++ b/app/Repos/PageRepo.php
@@ -614,8 +614,7 @@ class PageRepo extends EntityRepo
      */
     public function findSuitableSlug($name, $bookId, $currentId = false)
     {
-        $slug = Str::slug($name);
-        if ($slug === "") $slug = substr(md5(rand(1, 500)), 0, 5);
+        $slug = $this->nameToSlug($name);
         while ($this->doesSlugExist($slug, $bookId, $currentId)) {
             $slug .= '-' . substr(md5(rand(1, 500)), 0, 3);
         }