diff --git a/install/package.json b/install/package.json
index 0d05a4a220..dcf98231f3 100644
--- a/install/package.json
+++ b/install/package.json
@@ -69,9 +69,9 @@
         "nodebb-plugin-spam-be-gone": "0.5.1",
         "nodebb-rewards-essentials": "0.0.11",
         "nodebb-theme-lavender": "5.0.1",
-        "nodebb-theme-persona": "7.2.19",
+        "nodebb-theme-persona": "7.2.20",
         "nodebb-theme-slick": "1.1.4",
-        "nodebb-theme-vanilla": "8.1.7",
+        "nodebb-theme-vanilla": "8.1.9",
         "nodebb-widget-essentials": "4.0.1",
         "nodemailer": "4.4.1",
         "passport": "^0.4.0",
diff --git a/public/src/client/popular.js b/public/src/client/popular.js
index 02a1348156..b63f776bff 100644
--- a/public/src/client/popular.js
+++ b/public/src/client/popular.js
@@ -1,7 +1,7 @@
 'use strict';
 
 
-define('forum/popular', ['components'], function (components) {
+define('forum/popular', ['forum/recent', 'components', 'forum/infinitescroll'], function (recent, components, infinitescroll) {
 	var Popular = {};
 
 	Popular.init = function () {
@@ -11,7 +11,30 @@ define('forum/popular', ['components'], function (components) {
 			.removeClass('active')
 			.find('a[href="' + window.location.pathname + '"]')
 			.parent().addClass('active');
+
+		if (!config.usePagination) {
+			infinitescroll.init(loadMoreTopics);
+		}
 	};
 
+	function loadMoreTopics(direction) {
+		if (direction < 0 || !$('[component="category"]').length) {
+			return;
+		}
+
+		infinitescroll.loadMore('topics.loadMorePopularTopics', {
+			after: $('[component="category"]').attr('data-nextstart'),
+			count: config.topicsPerPage,
+			term: ajaxify.data.term,
+		}, function (data, done) {
+			if (data.topics && data.topics.length) {
+				recent.onTopicsLoaded('popular', data.topics, false, done);
+				$('[component="category"]').attr('data-nextstart', data.nextStart);
+			} else {
+				done();
+			}
+		});
+	}
+
 	return Popular;
 });
diff --git a/src/controllers/popular.js b/src/controllers/popular.js
index b6a189383c..6c7c2e6011 100644
--- a/src/controllers/popular.js
+++ b/src/controllers/popular.js
@@ -3,9 +3,12 @@
 
 var async = require('async');
 var nconf = require('nconf');
+
 var topics = require('../topics');
 var meta = require('../meta');
+var user = require('../user');
 var helpers = require('./helpers');
+var pagination = require('../pagination');
 
 var popularController = module.exports;
 
@@ -19,6 +22,7 @@ var terms = {
 };
 
 popularController.get = function (req, res, next) {
+	var page = parseInt(req.query.page, 10) || 1;
 	var term = terms[req.params.term];
 
 	if (!term && req.params.term) {
@@ -38,19 +42,25 @@ popularController.get = function (req, res, next) {
 			return res.render('popular', anonCache[term]);
 		}
 	}
-
+	var settings;
 	async.waterfall([
 		function (next) {
-			topics.getPopular(term, req.uid, meta.config.topicsPerList, next);
+			user.getSettings(req.uid, next);
 		},
-		function (topics) {
-			var data = {
-				title: meta.config.homePageTitle || '[[pages:home]]',
-				topics: topics,
-				'feeds:disableRSS': parseInt(meta.config['feeds:disableRSS'], 10) === 1,
-				rssFeedUrl: nconf.get('relative_path') + '/popular/' + (req.params.term || 'daily') + '.rss',
-				term: term,
-			};
+		function (_settings, next) {
+			settings = _settings;
+			var start = Math.max(0, (page - 1) * settings.topicsPerPage);
+			var stop = start + settings.topicsPerPage - 1;
+			topics.getPopularTopics(term, req.uid, start, stop, next);
+		},
+		function (data) {
+			var pageCount = Math.max(1, Math.ceil(data.topicCount / settings.topicsPerPage));
+
+			data.title = meta.config.homePageTitle || '[[pages:home]]';
+			data['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1;
+			data.rssFeedUrl = nconf.get('relative_path') + '/popular/' + (req.params.term || 'alltime') + '.rss';
+			data.term = term;
+			data.pagination = pagination.create(page, pageCount, req.query);
 
 			if (req.originalUrl.startsWith(nconf.get('relative_path') + '/api/popular') || req.originalUrl.startsWith(nconf.get('relative_path') + '/popular')) {
 				data.title = '[[pages:popular-' + term + ']]';
diff --git a/src/routes/feeds.js b/src/routes/feeds.js
index eba2fee4ee..0ed4cac217 100644
--- a/src/routes/feeds.js
+++ b/src/routes/feeds.js
@@ -252,16 +252,16 @@ function generateForPopular(req, res, next) {
 
 	async.waterfall([
 		function (next) {
-			topics.getPopular(term, req.uid, 19, next);
+			topics.getPopularTopics(term, req.uid, 0, 19, next);
 		},
-		function (topics, next) {
+		function (result, next) {
 			generateTopicsFeed({
 				uid: req.uid,
 				title: 'Popular Topics',
 				description: 'A list of topics that are sorted by post count',
 				feed_url: '/popular/' + (req.params.term || 'daily') + '.rss',
 				site_url: '/popular/' + (req.params.term || 'daily'),
-			}, topics, next);
+			}, result.topics, next);
 		},
 		function (feed) {
 			sendFeed(feed, res);
diff --git a/src/socket.io/topics/infinitescroll.js b/src/socket.io/topics/infinitescroll.js
index 18be5f6341..64fa087430 100644
--- a/src/socket.io/topics/infinitescroll.js
+++ b/src/socket.io/topics/infinitescroll.js
@@ -88,37 +88,37 @@ module.exports = function (SocketTopics) {
 	};
 
 	SocketTopics.loadMoreUnreadTopics = function (socket, data, callback) {
-		if (!data || !utils.isNumber(data.after) || parseInt(data.after, 10) < 0) {
-			return callback(new Error('[[error:invalid-data]]'));
-		}
-
-		var start = parseInt(data.after, 10);
-		var stop = start + Math.max(0, Math.min(meta.config.topicsPerPage || 20, parseInt(data.count, 10) || meta.config.topicsPerPage || 20) - 1);
-
-		topics.getUnreadTopics({ cid: data.cid, uid: socket.uid, start: start, stop: stop, filter: data.filter }, callback);
+		loadData(data, callback, function (start, stop) {
+			topics.getUnreadTopics({ cid: data.cid, uid: socket.uid, start: start, stop: stop, filter: data.filter }, callback);
+		});
 	};
 
 	SocketTopics.loadMoreRecentTopics = function (socket, data, callback) {
-		if (!data || !utils.isNumber(data.after) || parseInt(data.after, 10) < 0) {
-			return callback(new Error('[[error:invalid-data]]'));
-		}
-
-		var start = parseInt(data.after, 10);
-		var stop = start + Math.max(0, Math.min(meta.config.topicsPerPage || 20, parseInt(data.count, 10) || meta.config.topicsPerPage || 20) - 1);
+		loadData(data, callback, function (start, stop) {
+			topics.getRecentTopics(data.cid, socket.uid, start, stop, data.filter, callback);
+		});
+	};
 
-		topics.getRecentTopics(data.cid, socket.uid, start, stop, data.filter, callback);
+	SocketTopics.loadMorePopularTopics = function (socket, data, callback) {
+		loadData(data, callback, function (start, stop) {
+			topics.getPopularTopics(data.term, socket.uid, start, stop, callback);
+		});
 	};
 
 	SocketTopics.loadMoreTopTopics = function (socket, data, callback) {
+		loadData(data, callback, function (start, stop) {
+			topics.getTopTopics(data.cid, socket.uid, start, stop, data.filter, callback);
+		});
+	};
+
+	function loadData(data, callback, loadFn) {
 		if (!data || !utils.isNumber(data.after) || parseInt(data.after, 10) < 0) {
 			return callback(new Error('[[error:invalid-data]]'));
 		}
-
 		var start = parseInt(data.after, 10);
 		var stop = start + Math.max(0, Math.min(meta.config.topicsPerPage || 20, parseInt(data.count, 10) || meta.config.topicsPerPage || 20) - 1);
-
-		topics.getTopTopics(data.cid, socket.uid, start, stop, data.filter, callback);
-	};
+		loadFn(start, stop);
+	}
 
 	SocketTopics.loadMoreFromSet = function (socket, data, callback) {
 		if (!data || !utils.isNumber(data.after) || parseInt(data.after, 10) < 0 || !data.set) {
diff --git a/src/topics/popular.js b/src/topics/popular.js
index 85d25abeaf..99855b17d3 100644
--- a/src/topics/popular.js
+++ b/src/topics/popular.js
@@ -2,39 +2,50 @@
 'use strict';
 
 var async = require('async');
+
+var db = require('../database');
 var privileges = require('../privileges');
 
 module.exports = function (Topics) {
 	Topics.getPopular = function (term, uid, count, callback) {
 		count = parseInt(count, 10) || 20;
-
-		if (term === 'alltime') {
-			return getAllTimePopular(uid, count, callback);
-		}
-
 		async.waterfall([
 			function (next) {
-				Topics.getLatestTidsFromSet('topics:tid', 0, -1, term, next);
+				Topics.getPopularTopics(term, uid, 0, count - 1, next);
 			},
-			function (tids, next) {
-				getTopics(tids, uid, count, next);
+			function (data, next) {
+				next(null, data.topics);
 			},
 		], callback);
 	};
 
-	function getAllTimePopular(uid, count, callback) {
+	Topics.getPopularTopics = function (term, uid, start, stop, callback) {
+		var popularTopics = {
+			nextStart: 0,
+			topicCount: 0,
+			topics: [],
+		};
 		async.waterfall([
 			function (next) {
-				Topics.getTopicsFromSet('topics:posts', uid, 0, count - 1, next);
+				if (term === 'alltime') {
+					db.getSortedSetRevRange('topics:posts', 0, 199, next);
+				} else {
+					Topics.getLatestTidsFromSet('topics:tid', 0, -1, term, next);
+				}
 			},
-			function (data, next) {
-				data.topics.sort(sortPopular);
-				next(null, data.topics);
+			function (tids, next) {
+				popularTopics.topicCount = tids.length;
+				getTopics(tids, uid, start, stop, next);
+			},
+			function (topics, next) {
+				popularTopics.topics = topics;
+				popularTopics.nextStart = stop + 1;
+				next(null, popularTopics);
 			},
 		], callback);
-	}
+	};
 
-	function getTopics(tids, uid, count, callback) {
+	function getTopics(tids, uid, start, stop, callback) {
 		async.waterfall([
 			function (next) {
 				Topics.getTopicsFields(tids, ['tid', 'postcount', 'deleted'], next);
@@ -42,7 +53,7 @@ module.exports = function (Topics) {
 			function (topics, next) {
 				tids = topics.filter(function (topic) {
 					return topic && parseInt(topic.deleted, 10) !== 1;
-				}).sort(sortPopular).slice(0, count).map(function (topic) {
+				}).sort(sortPopular).slice(start, stop !== -1 ? stop - 1 : undefined).map(function (topic) {
 					return topic.tid;
 				});
 				privileges.topics.filterTids('read', tids, uid, next);
diff --git a/src/user/digest.js b/src/user/digest.js
index c08a0afe93..b1562aab3f 100644
--- a/src/user/digest.js
+++ b/src/user/digest.js
@@ -106,14 +106,14 @@ Digest.send = function (data, callback) {
 					function (next) {
 						async.parallel({
 							notifications: async.apply(user.notifications.getDailyUnread, userObj.uid),
-							topics: async.apply(topics.getPopular, data.interval, userObj.uid, 10),
+							popular: async.apply(topics.getPopularTopics, data.interval, userObj.uid, 0, 9),
 						}, next);
 					},
 					function (data, next) {
 						var notifications = data.notifications.filter(Boolean);
 
 						// If there are no notifications and no new topics, don't bother sending a digest
-						if (!notifications.length && !data.topics.length) {
+						if (!notifications.length && !data.popular.topics.length) {
 							return next();
 						}
 
@@ -124,7 +124,7 @@ Digest.send = function (data, callback) {
 						});
 
 						// Fix relative paths in topic data
-						data.topics = data.topics.map(function (topicObj) {
+						data.popular.topics = data.popular.topics.map(function (topicObj) {
 							var user = topicObj.hasOwnProperty('teaser') && topicObj.teaser !== undefined ? topicObj.teaser.user : topicObj.user;
 							if (user && user.picture && utils.isRelativeUrl(user.picture)) {
 								user.picture = nconf.get('base_url') + user.picture;
@@ -138,7 +138,7 @@ Digest.send = function (data, callback) {
 							username: userObj.username,
 							userslug: userObj.userslug,
 							notifications: notifications,
-							recent: data.topics,
+							recent: data.popular.topics,
 							interval: data.interval,
 							showUnsubscribe: true,
 						}, function (err) {