diff --git a/Makefile b/Makefile
index 9dfd4f89f..b70b616b8 100644
--- a/Makefile
+++ b/Makefile
@@ -18,10 +18,17 @@ test: check_go_env
 	go test ./... -v
 #	@(cd ./ui && npm test -- --watchAll=false)
 
+.PHONY: testall
+testall: check_go_env test
+	@(cd ./ui && npm test -- --watchAll=false)
+
 .PHONY: build
 build: check_go_env
 	go build
-#	@(cd ./ui && npm run build)
+
+.PHONY: build
+buildall: check_go_env build
+	@(cd ./ui && npm run build)
 
 .PHONY: setup
 setup: Jamstash-master
diff --git a/main.go b/main.go
index 556372e17..f93c333c9 100644
--- a/main.go
+++ b/main.go
@@ -7,7 +7,8 @@ import (
 func main() {
 	conf.Load()
 
-	a := CreateApp(conf.Sonic.MusicFolder)
-	a.MountRouter("/rest/", CreateSubsonicAPIRouter())
+	a := CreateServer(conf.Sonic.MusicFolder)
+	a.MountRouter("/rest", CreateSubsonicAPIRouter())
+	a.MountRouter("/app", CreateAppRouter("/app"))
 	a.Run(":" + conf.Sonic.Port)
 }
diff --git a/server/app/app.go b/server/app/app.go
new file mode 100644
index 000000000..a745b1b24
--- /dev/null
+++ b/server/app/app.go
@@ -0,0 +1,33 @@
+package app
+
+import (
+	"net/http"
+
+	"github.com/cloudsonic/sonic-server/model"
+	"github.com/cloudsonic/sonic-server/server"
+	"github.com/go-chi/chi"
+)
+
+type Router struct {
+	ds   model.DataStore
+	mux  http.Handler
+	path string
+}
+
+func New(ds model.DataStore, path string) *Router {
+	r := &Router{ds: ds, path: path}
+	r.mux = r.routes()
+	return r
+}
+
+func (app *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	app.mux.ServeHTTP(w, r)
+}
+
+func (app *Router) routes() http.Handler {
+	r := chi.NewRouter()
+	server.FileServer(r, app.path, "/", http.Dir("ui/build"))
+	r.Get("/ping", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(`{"response":"pong"}`)) })
+
+	return r
+}
diff --git a/server/fileserver.go b/server/fileserver.go
new file mode 100644
index 000000000..deedb52a8
--- /dev/null
+++ b/server/fileserver.go
@@ -0,0 +1,47 @@
+package server
+
+import (
+	"net/http"
+	"os"
+	"strings"
+
+	"github.com/go-chi/chi"
+)
+
+func FileServer(r chi.Router, fullPath, subPath string, root http.FileSystem) {
+	if strings.ContainsAny(fullPath, "{}*") {
+		panic("FileServer does not permit URL parameters.")
+	}
+
+	fs := http.StripPrefix(fullPath, http.FileServer(justFilesFilesystem{root}))
+
+	if subPath != "/" && subPath[len(subPath)-1] != '/' {
+		r.Get(subPath, http.RedirectHandler(fullPath+"/", 302).ServeHTTP)
+		subPath += "/"
+	}
+	subPath += "*"
+
+	r.Get(subPath, func(w http.ResponseWriter, r *http.Request) {
+		fs.ServeHTTP(w, r)
+	})
+}
+
+type justFilesFilesystem struct {
+	fs http.FileSystem
+}
+
+func (fs justFilesFilesystem) Open(name string) (http.File, error) {
+	f, err := fs.fs.Open(name)
+	if err != nil {
+		return nil, err
+	}
+	return neuteredReaddirFile{f}, nil
+}
+
+type neuteredReaddirFile struct {
+	http.File
+}
+
+func (f neuteredReaddirFile) Readdir(count int) ([]os.FileInfo, error) {
+	return nil, nil
+}
diff --git a/server/server.go b/server/server.go
index b4668c364..cfd283c46 100644
--- a/server/server.go
+++ b/server/server.go
@@ -4,7 +4,6 @@ import (
 	"net/http"
 	"os"
 	"path/filepath"
-	"strings"
 	"time"
 
 	"github.com/cloudsonic/sonic-server/conf"
@@ -34,7 +33,7 @@ func New(scanner *scanner.Scanner) *Server {
 }
 
 func (a *Server) MountRouter(path string, subRouter http.Handler) {
-	log.Info("Mounting API", "path", path)
+	log.Info("Mounting routes", "path", path)
 	a.router.Group(func(r chi.Router) {
 		r.Use(middleware.Logger)
 		r.Mount(path, subRouter)
@@ -58,11 +57,12 @@ func (a *Server) initRoutes() {
 	r.Use(InjectLogger)
 
 	r.Get("/", func(w http.ResponseWriter, r *http.Request) {
-		http.Redirect(w, r, "/Jamstash", 302)
+		http.Redirect(w, r, "/app", 302)
 	})
+
 	workDir, _ := os.Getwd()
 	filesDir := filepath.Join(workDir, "Jamstash-master/dist")
-	FileServer(r, "/Jamstash", http.Dir(filesDir))
+	FileServer(r, "/Jamstash", "/Jamstash", http.Dir(filesDir))
 
 	a.router = r
 }
@@ -81,24 +81,6 @@ func (a *Server) initScanner() {
 	}()
 }
 
-func FileServer(r chi.Router, path string, root http.FileSystem) {
-	if strings.ContainsAny(path, "{}*") {
-		panic("FileServer does not permit URL parameters.")
-	}
-
-	fs := http.StripPrefix(path, http.FileServer(root))
-
-	if path != "/" && path[len(path)-1] != '/' {
-		r.Get(path, http.RedirectHandler(path+"/", 301).ServeHTTP)
-		path += "/"
-	}
-	path += "*"
-
-	r.Get(path, func(w http.ResponseWriter, r *http.Request) {
-		fs.ServeHTTP(w, r)
-	})
-}
-
 func InjectLogger(next http.Handler) http.Handler {
 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		ctx := r.Context()
diff --git a/ui/package.json b/ui/package.json
index 6fc602e05..621cc64ad 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -19,7 +19,7 @@
     "test": "react-scripts test",
     "eject": "react-scripts eject"
   },
-  "homepage": "https://localhost/ui/",
+  "homepage": "https://localhost/app/",
   "proxy": "http://localhost:4633/",
   "eslintConfig": {
     "extends": "react-app"
diff --git a/wire_gen.go b/wire_gen.go
index 187113613..b2174e444 100644
--- a/wire_gen.go
+++ b/wire_gen.go
@@ -10,19 +10,26 @@ import (
 	"github.com/cloudsonic/sonic-server/persistence"
 	"github.com/cloudsonic/sonic-server/scanner"
 	"github.com/cloudsonic/sonic-server/server"
+	"github.com/cloudsonic/sonic-server/server/app"
 	"github.com/cloudsonic/sonic-server/server/subsonic"
 	"github.com/google/wire"
 )
 
 // Injectors from wire_injectors.go:
 
-func CreateApp(musicFolder string) *server.Server {
+func CreateServer(musicFolder string) *server.Server {
 	dataStore := persistence.New()
 	scannerScanner := scanner.New(dataStore)
 	serverServer := server.New(scannerScanner)
 	return serverServer
 }
 
+func CreateAppRouter(path string) *app.Router {
+	dataStore := persistence.New()
+	router := app.New(dataStore, path)
+	return router
+}
+
 func CreateSubsonicAPIRouter() *subsonic.Router {
 	dataStore := persistence.New()
 	browser := engine.NewBrowser(dataStore)
@@ -39,4 +46,4 @@ func CreateSubsonicAPIRouter() *subsonic.Router {
 
 // wire_injectors.go:
 
-var allProviders = wire.NewSet(engine.Set, scanner.New, subsonic.New, persistence.New)
+var allProviders = wire.NewSet(engine.Set, scanner.New, subsonic.New, app.New, persistence.New)
diff --git a/wire_injectors.go b/wire_injectors.go
index f4d333938..595ae09d7 100644
--- a/wire_injectors.go
+++ b/wire_injectors.go
@@ -7,6 +7,7 @@ import (
 	"github.com/cloudsonic/sonic-server/persistence"
 	"github.com/cloudsonic/sonic-server/scanner"
 	"github.com/cloudsonic/sonic-server/server"
+	"github.com/cloudsonic/sonic-server/server/app"
 	"github.com/cloudsonic/sonic-server/server/subsonic"
 	"github.com/google/wire"
 )
@@ -15,16 +16,21 @@ var allProviders = wire.NewSet(
 	engine.Set,
 	scanner.New,
 	subsonic.New,
+	app.New,
 	persistence.New,
 )
 
-func CreateApp(musicFolder string) *server.Server {
+func CreateServer(musicFolder string) *server.Server {
 	panic(wire.Build(
 		server.New,
 		allProviders,
 	))
 }
 
+func CreateAppRouter(path string) *app.Router {
+	panic(wire.Build(allProviders))
+}
+
 func CreateSubsonicAPIRouter() *subsonic.Router {
 	panic(wire.Build(allProviders))
 }