diff --git a/qlfile.lock b/qlfile.lock
index 946ad68..0d3e9f0 100644
--- a/qlfile.lock
+++ b/qlfile.lock
@@ -21,7 +21,7 @@
 ("ningle-fbr" .
  (:class qlot/source/git:source-git
   :initargs (:remote-url "https://github.com/skyizwhite/ningle-fbr.git")
-  :version "git-3c83e74a84e57f8ee2a9e98c4045d9b3e7a937f5"))
+  :version "git-19aae06f7ff17f16f008133a3bf234b89ab7de1e"))
 ("lack-mw" .
  (:class qlot/source/git:source-git
   :initargs (:remote-url "https://github.com/skyizwhite/lack-mw.git")
diff --git a/src/api/not-found.lisp b/src/api/not-found.lisp
new file mode 100644
index 0000000..8268ad5
--- /dev/null
+++ b/src/api/not-found.lisp
@@ -0,0 +1,9 @@
+(defpackage #:website/api/not-found
+  (:use #:cl
+        #:jingle)
+  (:export #:handle-not-found))
+(in-package #:website/api/not-found)
+
+(defun handle-not-found ()
+  (set-response-status :not-found)
+  '(:|message| "Not found"))
diff --git a/src/routes/api/revalidate.lisp b/src/api/revalidate.lisp
similarity index 88%
rename from src/routes/api/revalidate.lisp
rename to src/api/revalidate.lisp
index 7e3f1a0..b1ac2bf 100644
--- a/src/routes/api/revalidate.lisp
+++ b/src/api/revalidate.lisp
@@ -1,17 +1,17 @@
-(defpackage #:website/routes/api/revalidate
+(defpackage #:website/api/revalidate
   (:use #:cl
         #:jingle
         #:access)
   (:import-from #:website/lib/env
                 #:microcms-webhook-key)
   (:import-from #:website/helper
-                #:get-request-body-plist)
+                #:request-body-json->plist)
   (:import-from #:website/lib/cms
                 #:clear-about-cache
                 #:clear-works-cache
                 #:clear-blog-cache)
   (:export #:handle-post))
-(in-package #:website/routes/api/revalidate)
+(in-package #:website/api/revalidate)
 
 (defun handle-post (params)
   (declare (ignore params))
@@ -19,7 +19,7 @@
                    (microcms-webhook-key))
     (set-response-status :unauthorized)
     (return-from handle-post '(:|message| "Invalid token")))
-  (let* ((body (get-request-body-plist))
+  (let* ((body (request-body-json->plist))
          (api (getf body :|api|))
          (id (getf body :|id|))
          (old-draft-key (accesses body :|contents| :|old| :|draftKey|))
diff --git a/src/app.lisp b/src/app.lisp
index 957ef97..6e85b2e 100644
--- a/src/app.lisp
+++ b/src/app.lisp
@@ -1,27 +1,71 @@
 (defpackage #:website/app
   (:use #:cl
-        #:jingle)
+        #:jingle
+        #:hsx)
+  (:import-from #:jonathan
+                #:to-json)
   (:import-from #:ningle-fbr
                 #:set-routes)
+  (:import-from #:lack/middleware/mount
+                #:*lack-middleware-mount*)
   (:import-from #:lack-mw
                 #:*trim-trailing-slash*)
   (:import-from #:clack-errors
                 #:*clack-error-middleware*)
+  (:import-from #:website/components/metadata
+                #:~metadata)
+  (:import-from #:website/components/scripts
+                #:~scripts)
+  (:import-from #:website/components/layout
+                #:~layout)
   (:import-from #:website/lib/env
                 #:dev-mode-p)
-  (:import-from #:website/renderer)
   (:export #:*app*))
 (in-package #:website/app)
 
+(defparameter *page-app* (make-app))
+(set-routes *page-app* :system :website :target-dir-path "pages")
+
+(defmethod jingle:process-response :around ((app (eql *page-app*)) result)
+  (set-response-header :content-type "text/html; charset=utf-8")
+  (when (eq (request-method *request*) :get)
+    (let ((strategy (context :cache)))
+      (cond ((dev-mode-p)
+             (set-response-header :cache-control "private, no-store, must-revalidate"))
+            ((eq strategy :ssr)
+             (set-response-header :cache-control "public, max-age=0, must-revalidate"))
+            ((eq strategy :isr)
+             (set-response-header :cache-control "public, max-age=0, s-maxage=60, stale-while-revalidate=60"))
+            ((eq strategy :sg)
+             (set-response-header :cache-control "public, max-age=0, s-maxage=31536000, must-revalidate")))))
+  (call-next-method app
+                    (render-to-string
+                     (hsx (html :lang "en"
+                            (head
+                              (~metadata :metadata (context :metadata))
+                              (~scripts))
+                            (body
+                              (~layout result)))))))
+
+(defparameter *api-app* (make-app))
+(set-routes *api-app* :system :website :target-dir-path "api")
+
+(defmethod jingle:process-response :around ((app (eql *api-app*)) result)
+  (set-response-header :content-type "application/json; charset=utf-8") 
+  (call-next-method app (to-json result)))
+
+(defun with-args (middleware &rest args)
+  (lambda (app)
+    (apply middleware app args)))
+
 (defparameter *app*
-  (let ((app (make-app)))
-    (set-routes app :system :website :target-dir-path "routes")
-    (install-middleware app (lambda (app)
-                              (funcall *clack-error-middleware*
-                                       app
-                                       :debug (dev-mode-p))))
-    (install-middleware app *trim-trailing-slash*)
-    (static-path app "/assets/" "assets/")
-    (configure app)))
+  (progn
+    (install-middleware *page-app*
+                        (with-args *lack-middleware-mount* "/api" *api-app*))
+    (install-middleware *page-app* 
+                        (with-args *clack-error-middleware* :debug (dev-mode-p)))
+    (install-middleware *page-app* *trim-trailing-slash*)
+    (static-path *page-app* "/assets/" "assets/")
+    (configure *page-app*)))
 
 *app*
diff --git a/src/helper.lisp b/src/helper.lisp
index c208395..d2fe236 100644
--- a/src/helper.lisp
+++ b/src/helper.lisp
@@ -5,18 +5,12 @@
                 #:make-flexi-stream)
   (:import-from #:jonathan
                 #:parse)
-  (:export #:api-request-p
-           #:get-request-body-plist))
+  (:export #:request-body-json->plist
+           #:set-metadata
+           #:set-cache))
 (in-package #:website/helper)
 
-(defun starts-with-p (prefix string)
-  (let ((pos (search prefix string :start1 0 :end1 (length prefix) :start2 0)))
-    (and pos (= pos 0))))
-
-(defun api-request-p ()
-  (starts-with-p "/api/" (request-uri *request*)))
-
-(defun get-request-body-plist ()
+(defun request-body-json->plist ()
   (parse
    (let ((text-stream (make-flexi-stream (request-raw-body *request*)
                                          :external-format :utf-8)))
@@ -24,3 +18,9 @@
        (loop :for char := (read-char text-stream nil)
              :while char
              :do (write-char char out))))))
+
+(defun set-metadata (metadata)
+  (setf (context :metadata) metadata))
+
+(defun set-cache (strategy)
+  (setf (context :cache) strategy))
diff --git a/src/routes/about.lisp b/src/pages/about.lisp
similarity index 74%
rename from src/routes/about.lisp
rename to src/pages/about.lisp
index 99ca377..06dab91 100644
--- a/src/routes/about.lisp
+++ b/src/pages/about.lisp
@@ -1,21 +1,22 @@
-(defpackage #:website/routes/about
+(defpackage #:website/pages/about
   (:use #:cl
         #:hsx
-        #:jingle)
+        #:jingle
+        #:website/helper)
   (:import-from #:website/lib/cms
                 #:fetch-about)
   (:import-from #:website/components/article
                 #:~article)
   (:export #:handle-get))
-(in-package #:website/routes/about)
+(in-package #:website/pages/about)
 
 (defparameter *metadata*
   (list :title "about"))
 
 (defun handle-get (params)
-  (setf (context :metadata) *metadata*)
+  (set-metadata *metadata*)
   (with-request-params ((draft-key "draft-key" nil)) params
-    (setf (context :cache) (if draft-key :ssr :isr))
+    (set-cache (if draft-key :ssr :isr))
     (let ((about (fetch-about :draft-key draft-key)))
       (~article
         :title "About"
diff --git a/src/routes/blog/<id>.lisp b/src/pages/blog/<id>.lisp
similarity index 64%
rename from src/routes/blog/<id>.lisp
rename to src/pages/blog/<id>.lisp
index 43d2d2a..2093ea9 100644
--- a/src/routes/blog/<id>.lisp
+++ b/src/pages/blog/<id>.lisp
@@ -1,15 +1,16 @@
-(defpackage #:website/routes/blog/<id>
+(defpackage #:website/pages/blog/<id>
   (:use #:cl
         #:hsx
-        #:jingle)
+        #:jingle
+        #:website/helper)
   (:import-from #:website/lib/cms
                 #:fetch-blog-detail)
-  (:import-from #:website/routes/not-found
+  (:import-from #:website/pages/not-found
                 #:handle-not-found)
   (:import-from #:website/components/article
                 #:~article)
   (:export #:handle-get))
-(in-package #:website/routes/blog/<id>)
+(in-package #:website/pages/blog/<id>)
 
 (defun handle-get (params)
   (with-request-params ((id :id nil)
@@ -17,10 +18,10 @@
     (let ((blog (fetch-blog-detail id :draft-key draft-key)))
       (unless blog
         (return-from handle-get (handle-not-found)))
-      (setf (context :cache) (if draft-key :ssr :isr))
-      (setf (context :metadata) (list :title (getf blog :title)
-                                      :description (getf blog :description)
-                                      :type "article"))
+      (set-cache (if draft-key :ssr :isr))
+      (set-metadata (list :title (getf blog :title)
+                          :description (getf blog :description)
+                          :type "article"))
       (hsx
        (~article
          :title (getf blog :title)
diff --git a/src/routes/blog/index.lisp b/src/pages/blog/index.lisp
similarity index 84%
rename from src/routes/blog/index.lisp
rename to src/pages/blog/index.lisp
index ff668c6..8bc73d3 100644
--- a/src/routes/blog/index.lisp
+++ b/src/pages/blog/index.lisp
@@ -1,21 +1,22 @@
-(defpackage #:website/routes/blog/index
+(defpackage #:website/pages/blog/index
   (:use #:cl
         #:hsx
-        #:jingle)
+        #:jingle
+        #:website/helper)
   (:import-from #:website/lib/cms
                 #:fetch-blog-list)
   (:import-from #:website/lib/time
                 #:asctime)
   (:export #:handle-get))
-(in-package #:website/routes/blog/index)
+(in-package #:website/pages/blog/index)
 
 (defparameter *metadata*
   (list :title "blog"))
 
 (defun handle-get (params)
   (declare (ignore params))
-  (setf (context :cache) :isr)
-  (setf (context :metadata) *metadata*)
+  (set-cache :isr)
+  (set-metadata *metadata*)
   (let ((blogs (fetch-blog-list :page 1)))
     (hsx
      (section
diff --git a/src/routes/index.lisp b/src/pages/index.lisp
similarity index 92%
rename from src/routes/index.lisp
rename to src/pages/index.lisp
index 663a7ae..74c4565 100644
--- a/src/routes/index.lisp
+++ b/src/pages/index.lisp
@@ -1,13 +1,14 @@
-(defpackage #:website/routes/index
+(defpackage #:website/pages/index
   (:use #:cl
         #:hsx
         #:access
-        #:jingle)
+        #:jingle
+        #:website/helper)
   (:import-from #:website/lib/cms
                 #:get-about)
   (:export #:handle-get
            #:handle-head))
-(in-package #:website/routes/index)
+(in-package #:website/pages/index)
 
 (defparameter *links*
   '(("Keyoxide"
@@ -28,7 +29,7 @@
 
 (defun handle-get (params)
   (declare (ignore params))
-  (setf (context :cache) :sg)
+  (set-cache :sg)
   (hsx
    (div :class "flex flex-col items-center justify-center h-full"
      (img 
diff --git a/src/pages/not-found.lisp b/src/pages/not-found.lisp
new file mode 100644
index 0000000..80e7e2e
--- /dev/null
+++ b/src/pages/not-found.lisp
@@ -0,0 +1,23 @@
+(defpackage #:website/pages/not-found
+  (:use #:cl
+        #:hsx
+        #:jingle
+        #:website/helper)
+  (:export #:handle-not-found))
+(in-package #:website/pages/not-found)
+
+(defparameter *metadata*
+  '(:title "404 Not Found"
+    :description "The page you are looking for may have been deleted or the URL might be incorrect."
+    :error t))
+
+(defun handle-not-found ()
+  (set-response-status :not-found)
+  (set-cache :ssr)
+  (set-metadata *metadata*)
+  (hsx
+   (div :class "flex flex-col h-full items-center justify-center gap-y-6"
+     (h1 :class "font-bold text-2xl"
+       "404 Not Found")
+     (a :href "/" :class "text-lg text-pink-500 hover:underline"
+       "Back to TOP"))))
diff --git a/src/routes/works.lisp b/src/pages/works.lisp
similarity index 74%
rename from src/routes/works.lisp
rename to src/pages/works.lisp
index 68da918..6f8f75e 100644
--- a/src/routes/works.lisp
+++ b/src/pages/works.lisp
@@ -1,21 +1,22 @@
-(defpackage #:website/routes/works
+(defpackage #:website/pages/works
   (:use #:cl
         #:hsx
-        #:jingle)
+        #:jingle
+        #:website/helper)
   (:import-from #:website/lib/cms
                 #:fetch-works)
   (:import-from #:website/components/article
                 #:~article)
   (:export #:handle-get))
-(in-package #:website/routes/works)
+(in-package #:website/pages/works)
 
 (defparameter *metadata*
   (list :title "works"))
 
 (defun handle-get (params)
-  (setf (context :metadata) *metadata*)
+  (set-metadata *metadata*)
   (with-request-params ((draft-key "draft-key" nil)) params
-    (setf (context :cache) (if draft-key :ssr :isr))
+    (set-cache (if draft-key :ssr :isr))
     (let ((works (fetch-works :draft-key draft-key)))
       (~article
         :title "Works"
diff --git a/src/renderer.lisp b/src/renderer.lisp
deleted file mode 100644
index 61bb55a..0000000
--- a/src/renderer.lisp
+++ /dev/null
@@ -1,42 +0,0 @@
-(defpackage #:website/renderer
-  (:use #:cl
-        #:hsx
-        #:jingle)
-  (:import-from #:jonathan
-                #:to-json)
-  (:import-from #:website/lib/env
-                #:dev-mode-p)
-  (:import-from #:website/helper
-                #:api-request-p)
-  (:import-from #:website/components/metadata
-                #:~metadata)
-  (:import-from #:website/components/scripts
-                #:~scripts)
-  (:import-from #:website/components/layout
-                #:~layout))
-(in-package #:website/renderer)
-
-(defmethod jingle:process-response :around ((app jingle:app) result)
-  (when (eq (request-method *request*) :get)
-    (let ((strategy (context :cache)))
-      (cond ((dev-mode-p)
-             (set-response-header :cache-control "private, no-store, must-revalidate"))
-            ((eq strategy :ssr)
-             (set-response-header :cache-control "public, max-age=0, must-revalidate"))
-            ((eq strategy :isr)
-             (set-response-header :cache-control "public, max-age=0, s-maxage=60, stale-while-revalidate=60"))
-            ((eq strategy :sg)
-             (set-response-header :cache-control "public, max-age=0, s-maxage=31536000, must-revalidate")))))
-  (cond ((api-request-p)
-         (set-response-header :content-type "application/json; charset=utf-8") 
-         (call-next-method app (to-json result)))
-        (t
-         (set-response-header :content-type "text/html; charset=utf-8")
-         (call-next-method app
-                           (render-to-string
-                            (hsx (html :lang "en"
-                                   (head
-                                     (~metadata :metadata (context :metadata))
-                                     (~scripts))
-                                   (body
-                                     (~layout result)))))))))
diff --git a/src/routes/not-found.lisp b/src/routes/not-found.lisp
deleted file mode 100644
index dc6a623..0000000
--- a/src/routes/not-found.lisp
+++ /dev/null
@@ -1,26 +0,0 @@
-(defpackage #:website/routes/not-found
-  (:use #:cl
-        #:hsx
-        #:jingle)
-  (:import-from #:website/helper
-                #:api-request-p)
-  (:export #:handle-not-found))
-(in-package #:website/routes/not-found)
-
-(defparameter *metadata*
-  '(:title "404 Not Found"
-    :description "The page you are looking for may have been deleted or the URL might be incorrect."
-    :error t))
-
-(defun handle-not-found ()
-  (set-response-status :not-found)
-  (setf (context :cache) :ssr)
-  (setf (context :metadata) *metadata*)
-  (if (api-request-p)
-      '(:|message| "404 Not Found")
-      (hsx
-       (div :class "flex flex-col h-full items-center justify-center gap-y-6"
-         (h1 :class "font-bold text-2xl"
-           "404 Not Found")
-         (a :href "/" :class "text-lg text-pink-500 hover:underline"
-           "Back to TOP")))))
diff --git a/tailwind.config.js b/tailwind.config.js
index 738e1dd..31d339b 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -1,8 +1,7 @@
 /** @type {import('tailwindcss').Config} */
 module.exports = {
   content: [
-    "./src/renderer.lisp",
-    "./src/routes/**/*.lisp",
+    "./src/pages/**/*.lisp",
     "./src/components/**/*.lisp",
   ],
   theme: {