diff --git a/pkg/app/app.go b/pkg/app/app.go
index 1f6b769..0efd22a 100644
--- a/pkg/app/app.go
+++ b/pkg/app/app.go
@@ -8,14 +8,9 @@ import (
 	"log"
 	"net"
 	"net/http"
-	"net/url"
-	"os"
 	"path"
-	"strconv"
-	"time"
 
 	"github.com/fsnotify/fsnotify"
-	"github.com/gorilla/feeds"
 	"github.com/gorilla/mux"
 	"github.com/wybiral/tube/pkg/media"
 	"github.com/wybiral/tube/pkg/onionkey"
@@ -27,6 +22,7 @@ type App struct {
 	Library   *media.Library
 	Watcher   *fsnotify.Watcher
 	Templates *template.Template
+	Feed      []byte
 	Tor       *tor
 	Listener  net.Listener
 	Router    *mux.Router
@@ -123,6 +119,7 @@ func (a *App) Run() error {
 		}
 		a.Watcher.Add(p.Path)
 	}
+	buildFeed(a)
 	go startWatcher(a)
 	return http.Serve(a.Listener, a.Router)
 }
@@ -219,63 +216,7 @@ func (a *App) thumbHandler(w http.ResponseWriter, r *http.Request) {
 
 // HTTP handler for /feed.xml
 func (a *App) rssHandler(w http.ResponseWriter, r *http.Request) {
-	cfg := a.Config.Feed
-	now := time.Now()
-	f := &feeds.Feed{
-		Title:       cfg.Title,
-		Link:        &feeds.Link{Href: cfg.Link},
-		Description: cfg.Description,
-		Author: &feeds.Author{
-			Name:  cfg.Author.Name,
-			Email: cfg.Author.Email,
-		},
-		Created:   now,
-		Copyright: cfg.Copyright,
-	}
-	var externalURL string
-	if len(cfg.ExternalURL) > 0 {
-		externalURL = cfg.ExternalURL
-	} else if a.Tor != nil {
-		onion, err := a.Tor.OnionKey.Onion()
-		if err != nil {
-			return
-		}
-		externalURL = fmt.Sprintf("http://%s.onion", onion.ServiceID)
-	} else {
-		hostname, err := os.Hostname()
-		if err != nil {
-			host := a.Config.Server.Host
-			port := a.Config.Server.Port
-			externalURL = fmt.Sprintf("http://%s:%d", host, port)
-		} else {
-			externalURL = fmt.Sprintf("http://%s", hostname)
-		}
-	}
-	for _, v := range a.Library.Playlist() {
-		u, err := url.Parse(externalURL)
-		if err != nil {
-			return
-		}
-		u.Path = path.Join(u.Path, "v", v.ID)
-		id := u.String()
-		f.Items = append(f.Items, &feeds.Item{
-			Id:          id,
-			Title:       v.Title,
-			Link:        &feeds.Link{Href: id},
-			Description: v.Description,
-			Enclosure: &feeds.Enclosure{
-				Url:    id + ".mp4",
-				Length: strconv.FormatInt(v.Size, 10),
-				Type:   "video/mp4",
-			},
-			Author: &feeds.Author{
-				Name:  cfg.Author.Name,
-				Email: cfg.Author.Email,
-			},
-			Created: v.Timestamp,
-		})
-	}
 	w.Header().Set("Cache-Control", "public, max-age=7776000")
 	w.Header().Set("Content-Type", "text/xml")
-	f.WriteRss(w)
+	w.Write(a.Feed)
 }
diff --git a/pkg/app/feed.go b/pkg/app/feed.go
new file mode 100644
index 0000000..e4c1ce2
--- /dev/null
+++ b/pkg/app/feed.go
@@ -0,0 +1,77 @@
+package app
+
+import (
+	"fmt"
+	"net/url"
+	"os"
+	"path"
+	"strconv"
+	"time"
+
+	"github.com/wybiral/feeds"
+)
+
+// buildFeed creates RSS feed attribute for App based on Library contents.
+func buildFeed(a *App) {
+	cfg := a.Config.Feed
+	now := time.Now()
+	f := &feeds.Feed{
+		Title:       cfg.Title,
+		Link:        &feeds.Link{Href: cfg.Link},
+		Description: cfg.Description,
+		Author: &feeds.Author{
+			Name:  cfg.Author.Name,
+			Email: cfg.Author.Email,
+		},
+		Created:   now,
+		Copyright: cfg.Copyright,
+	}
+	var externalURL string
+	if len(cfg.ExternalURL) > 0 {
+		externalURL = cfg.ExternalURL
+	} else if a.Tor != nil {
+		onion, err := a.Tor.OnionKey.Onion()
+		if err != nil {
+			return
+		}
+		externalURL = fmt.Sprintf("http://%s.onion", onion.ServiceID)
+	} else {
+		hostname, err := os.Hostname()
+		if err != nil {
+			host := a.Config.Server.Host
+			port := a.Config.Server.Port
+			externalURL = fmt.Sprintf("http://%s:%d", host, port)
+		} else {
+			externalURL = fmt.Sprintf("http://%s", hostname)
+		}
+	}
+	for _, v := range a.Library.Playlist() {
+		u, err := url.Parse(externalURL)
+		if err != nil {
+			return
+		}
+		u.Path = path.Join(u.Path, "v", v.ID)
+		id := u.String()
+		f.Items = append(f.Items, &feeds.Item{
+			Id:          id,
+			Title:       v.Title,
+			Link:        &feeds.Link{Href: id},
+			Description: v.Description,
+			Enclosure: &feeds.Enclosure{
+				Url:    id + ".mp4",
+				Length: strconv.FormatInt(v.Size, 10),
+				Type:   "video/mp4",
+			},
+			Author: &feeds.Author{
+				Name:  cfg.Author.Name,
+				Email: cfg.Author.Email,
+			},
+			Created: v.Timestamp,
+		})
+	}
+	feed, err := f.ToRss()
+	if err != nil {
+		return
+	}
+	a.Feed = []byte(feed)
+}
diff --git a/pkg/app/watcher.go b/pkg/app/watcher.go
index 2d7a761..624e470 100644
--- a/pkg/app/watcher.go
+++ b/pkg/app/watcher.go
@@ -36,6 +36,7 @@ func startWatcher(a *App) {
 			// reset timer
 			timer.Reset(debounceTimeout)
 		case <-timer.C:
+			eventCount := len(removeEvents) + len(addEvents)
 			// handle remove events first
 			if len(removeEvents) > 0 {
 				for p := range removeEvents {
@@ -52,6 +53,9 @@ func startWatcher(a *App) {
 				// clear map
 				addEvents = make(map[string]struct{})
 			}
+			if eventCount > 0 {
+				buildFeed(a)
+			}
 			// reset timer
 			timer.Reset(debounceTimeout)
 		}