diff --git a/models/issue.go b/models/issue.go
index d40b81eb3..709ebc35f 100644
--- a/models/issue.go
+++ b/models/issue.go
@@ -1057,6 +1057,7 @@ type IssuesOptions struct {
 	MilestoneID int64
 	RepoIDs     []int64
 	Page        int
+	PageSize    int
 	IsClosed    util.OptionalBool
 	IsPull      util.OptionalBool
 	Labels      string
@@ -1085,21 +1086,16 @@ func sortIssuesSession(sess *xorm.Session, sortType string) {
 	}
 }
 
-// Issues returns a list of issues by given conditions.
-func Issues(opts *IssuesOptions) ([]*Issue, error) {
-	var sess *xorm.Session
-	if opts.Page >= 0 {
+func (opts *IssuesOptions) setupSession(sess *xorm.Session) error {
+	if opts.Page >= 0 && opts.PageSize > 0 {
 		var start int
 		if opts.Page == 0 {
 			start = 0
 		} else {
-			start = (opts.Page - 1) * setting.UI.IssuePagingNum
+			start = (opts.Page - 1) * opts.PageSize
 		}
-		sess = x.Limit(setting.UI.IssuePagingNum, start)
-	} else {
-		sess = x.NewSession()
+		sess.Limit(opts.PageSize, start)
 	}
-	defer sess.Close()
 
 	if len(opts.IssueIDs) > 0 {
 		sess.In("issue.id", opts.IssueIDs)
@@ -1144,12 +1140,10 @@ func Issues(opts *IssuesOptions) ([]*Issue, error) {
 		sess.And("issue.is_pull=?", false)
 	}
 
-	sortIssuesSession(sess, opts.SortType)
-
 	if len(opts.Labels) > 0 && opts.Labels != "0" {
 		labelIDs, err := base.StringsToInt64s(strings.Split(opts.Labels, ","))
 		if err != nil {
-			return nil, err
+			return err
 		}
 		if len(labelIDs) > 0 {
 			sess.
@@ -1157,6 +1151,45 @@ func Issues(opts *IssuesOptions) ([]*Issue, error) {
 				In("issue_label.label_id", labelIDs)
 		}
 	}
+	return nil
+}
+
+// CountIssuesByRepo map from repoID to number of issues matching the options
+func CountIssuesByRepo(opts *IssuesOptions) (map[int64]int64, error) {
+	sess := x.NewSession()
+	defer sess.Close()
+
+	if err := opts.setupSession(sess); err != nil {
+		return nil, err
+	}
+
+	countsSlice := make([]*struct {
+		RepoID int64
+		Count  int64
+	}, 0, 10)
+	if err := sess.GroupBy("issue.repo_id").
+		Select("issue.repo_id AS repo_id, COUNT(*) AS count").
+		Table("issue").
+		Find(&countsSlice); err != nil {
+		return nil, err
+	}
+
+	countMap := make(map[int64]int64, len(countsSlice))
+	for _, c := range countsSlice {
+		countMap[c.RepoID] = c.Count
+	}
+	return countMap, nil
+}
+
+// Issues returns a list of issues by given conditions.
+func Issues(opts *IssuesOptions) ([]*Issue, error) {
+	sess := x.NewSession()
+	defer sess.Close()
+
+	if err := opts.setupSession(sess); err != nil {
+		return nil, err
+	}
+	sortIssuesSession(sess, opts.SortType)
 
 	issues := make([]*Issue, 0, setting.UI.IssuePagingNum)
 	if err := sess.Find(&issues); err != nil {
diff --git a/models/issue_indexer.go b/models/issue_indexer.go
index c2cb8b429..05b324f53 100644
--- a/models/issue_indexer.go
+++ b/models/issue_indexer.go
@@ -133,7 +133,6 @@ func populateIssueIndexer() error {
 				RepoID:   repo.ID,
 				IsClosed: util.OptionalBoolNone,
 				IsPull:   util.OptionalBoolNone,
-				Page:     -1, // do not page
 			})
 			if err != nil {
 				return fmt.Errorf("Issues: %v", err)
diff --git a/models/main_test.go b/models/main_test.go
index 57e72a57f..451b5e4b2 100644
--- a/models/main_test.go
+++ b/models/main_test.go
@@ -8,11 +8,8 @@ import (
 
 	"code.gitea.io/gitea/modules/setting"
 
-	"github.com/go-xorm/core"
-	"github.com/go-xorm/xorm"
 	_ "github.com/mattn/go-sqlite3" // for the test engine
 	"github.com/stretchr/testify/assert"
-	"gopkg.in/testfixtures.v2"
 )
 
 // TestFixturesAreConsistent assert that test fixtures are consistent
@@ -21,23 +18,8 @@ func TestFixturesAreConsistent(t *testing.T) {
 	CheckConsistencyForAll(t)
 }
 
-// CreateTestEngine create an xorm engine for testing
-func CreateTestEngine() error {
-	var err error
-	x, err = xorm.NewEngine("sqlite3", "file::memory:?cache=shared")
-	if err != nil {
-		return err
-	}
-	x.SetMapper(core.GonicMapper{})
-	if err = x.StoreEngine("InnoDB").Sync2(tables...); err != nil {
-		return err
-	}
-
-	return InitFixtures(&testfixtures.SQLite{}, "fixtures/")
-}
-
 func TestMain(m *testing.M) {
-	if err := CreateTestEngine(); err != nil {
+	if err := CreateTestEngine("fixtures/"); err != nil {
 		fmt.Printf("Error creating test engine: %v\n", err)
 		os.Exit(1)
 	}
diff --git a/models/repo_list.go b/models/repo_list.go
index a2dae85c8..2158cfe67 100644
--- a/models/repo_list.go
+++ b/models/repo_list.go
@@ -15,6 +15,11 @@ import (
 // RepositoryList contains a list of repositories
 type RepositoryList []*Repository
 
+// RepositoryListOfMap make list from values of map
+func RepositoryListOfMap(repoMap map[int64]*Repository) RepositoryList {
+	return RepositoryList(valuesRepository(repoMap))
+}
+
 func (repos RepositoryList) loadAttributes(e Engine) error {
 	if len(repos) == 0 {
 		return nil
diff --git a/models/unit_tests.go b/models/unit_tests.go
index b4b36ba6b..315627d8e 100644
--- a/models/unit_tests.go
+++ b/models/unit_tests.go
@@ -7,13 +7,31 @@ package models
 import (
 	"testing"
 
+	"github.com/go-xorm/core"
 	"github.com/go-xorm/xorm"
 	"github.com/stretchr/testify/assert"
+	"gopkg.in/testfixtures.v2"
 )
 
 // NonexistentID an ID that will never exist
 const NonexistentID = 9223372036854775807
 
+// CreateTestEngine create in-memory sqlite database for unit tests
+// Any package that calls this must import github.com/mattn/go-sqlite3
+func CreateTestEngine(fixturesDir string) error {
+	var err error
+	x, err = xorm.NewEngine("sqlite3", "file::memory:?cache=shared")
+	if err != nil {
+		return err
+	}
+	x.SetMapper(core.GonicMapper{})
+	if err = x.StoreEngine("InnoDB").Sync2(tables...); err != nil {
+		return err
+	}
+
+	return InitFixtures(&testfixtures.SQLite{}, fixturesDir)
+}
+
 // PrepareTestDatabase load test fixtures into test database
 func PrepareTestDatabase() error {
 	return LoadFixtures()
diff --git a/modules/test/context_tests.go b/modules/test/context_tests.go
new file mode 100644
index 000000000..6bb7ffe98
--- /dev/null
+++ b/modules/test/context_tests.go
@@ -0,0 +1,150 @@
+// Copyright 2017 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package test
+
+import (
+	"net/http"
+	"net/url"
+	"testing"
+
+	"code.gitea.io/gitea/modules/context"
+
+	"github.com/stretchr/testify/assert"
+	macaron "gopkg.in/macaron.v1"
+)
+
+// MockContext mock context for unit tests
+func MockContext(t *testing.T) *context.Context {
+	var macaronContext *macaron.Context
+	mac := macaron.New()
+	mac.Get("*/", func(ctx *macaron.Context) {
+		macaronContext = ctx
+	})
+	req, err := http.NewRequest("GET", "star", nil)
+	assert.NoError(t, err)
+	req.Form = url.Values{}
+	mac.ServeHTTP(&mockResponseWriter{}, req)
+	assert.NotNil(t, macaronContext)
+	assert.EqualValues(t, req, macaronContext.Req.Request)
+	macaronContext.Locale = &mockLocale{}
+	macaronContext.Resp = &mockResponseWriter{}
+	macaronContext.Render = &mockRender{ResponseWriter: macaronContext.Resp}
+	return &context.Context{
+		Context: macaronContext,
+	}
+}
+
+type mockLocale struct{}
+
+func (l mockLocale) Language() string {
+	return "en"
+}
+
+func (l mockLocale) Tr(s string, _ ...interface{}) string {
+	return "test translation"
+}
+
+type mockResponseWriter struct {
+	status int
+	size   int
+}
+
+func (rw *mockResponseWriter) Header() http.Header {
+	return map[string][]string{}
+}
+
+func (rw *mockResponseWriter) Write(b []byte) (int, error) {
+	rw.size += len(b)
+	return len(b), nil
+}
+
+func (rw *mockResponseWriter) WriteHeader(status int) {
+	rw.status = status
+}
+
+func (rw *mockResponseWriter) Flush() {
+}
+
+func (rw *mockResponseWriter) Status() int {
+	return rw.status
+}
+
+func (rw *mockResponseWriter) Written() bool {
+	return rw.status > 0
+}
+
+func (rw *mockResponseWriter) Size() int {
+	return rw.size
+}
+
+func (rw *mockResponseWriter) Before(b macaron.BeforeFunc) {
+	b(rw)
+}
+
+type mockRender struct {
+	http.ResponseWriter
+}
+
+func (tr *mockRender) SetResponseWriter(rw http.ResponseWriter) {
+	tr.ResponseWriter = rw
+}
+
+func (tr *mockRender) JSON(int, interface{}) {
+}
+
+func (tr *mockRender) JSONString(interface{}) (string, error) {
+	return "", nil
+}
+
+func (tr *mockRender) RawData(status int, _ []byte) {
+	tr.Status(status)
+}
+
+func (tr *mockRender) PlainText(status int, _ []byte) {
+	tr.Status(status)
+}
+
+func (tr *mockRender) HTML(status int, _ string, _ interface{}, _ ...macaron.HTMLOptions) {
+	tr.Status(status)
+}
+
+func (tr *mockRender) HTMLSet(status int, _ string, _ string, _ interface{}, _ ...macaron.HTMLOptions) {
+	tr.Status(status)
+}
+
+func (tr *mockRender) HTMLSetString(string, string, interface{}, ...macaron.HTMLOptions) (string, error) {
+	return "", nil
+}
+
+func (tr *mockRender) HTMLString(string, interface{}, ...macaron.HTMLOptions) (string, error) {
+	return "", nil
+}
+
+func (tr *mockRender) HTMLSetBytes(string, string, interface{}, ...macaron.HTMLOptions) ([]byte, error) {
+	return nil, nil
+}
+
+func (tr *mockRender) HTMLBytes(string, interface{}, ...macaron.HTMLOptions) ([]byte, error) {
+	return nil, nil
+}
+
+func (tr *mockRender) XML(status int, _ interface{}) {
+	tr.Status(status)
+}
+
+func (tr *mockRender) Error(status int, _ ...string) {
+	tr.Status(status)
+}
+
+func (tr *mockRender) Status(status int) {
+	tr.ResponseWriter.WriteHeader(status)
+}
+
+func (tr *mockRender) SetTemplatePath(string, string) {
+}
+
+func (tr *mockRender) HasTemplateSet(string) bool {
+	return true
+}
diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go
index 253bdb43d..9f51022f3 100644
--- a/routers/api/v1/repo/issue.go
+++ b/routers/api/v1/repo/issue.go
@@ -31,6 +31,7 @@ func ListIssues(ctx *context.APIContext) {
 	issues, err := models.Issues(&models.IssuesOptions{
 		RepoID:   ctx.Repo.Repository.ID,
 		Page:     ctx.QueryInt("page"),
+		PageSize: setting.UI.IssuePagingNum,
 		IsClosed: isClosed,
 	})
 	if err != nil {
diff --git a/routers/repo/issue.go b/routers/repo/issue.go
index 473e82971..e4ed10d98 100644
--- a/routers/repo/issue.go
+++ b/routers/repo/issue.go
@@ -193,6 +193,7 @@ func Issues(ctx *context.Context) {
 			MentionedID: mentionedID,
 			MilestoneID: milestoneID,
 			Page:        pager.Current(),
+			PageSize:    setting.UI.IssuePagingNum,
 			IsClosed:    util.OptionalBoolOf(isShowClosed),
 			IsPull:      util.OptionalBoolOf(isPullList),
 			Labels:      selectLabels,
diff --git a/routers/user/home.go b/routers/user/home.go
index 712889069..b2096bc2c 100644
--- a/routers/user/home.go
+++ b/routers/user/home.go
@@ -270,94 +270,77 @@ func Issues(ctx *context.Context) {
 		userRepoIDs = []int64{-1}
 	}
 
-	var issues []*models.Issue
-	switch filterMode {
-	case models.FilterModeAll:
-		// Get all issues from repositories from this user.
-		issues, err = models.Issues(&models.IssuesOptions{
-			RepoIDs:  userRepoIDs,
-			RepoID:   repoID,
-			Page:     page,
-			IsClosed: util.OptionalBoolOf(isShowClosed),
-			IsPull:   util.OptionalBoolOf(isPullList),
-			SortType: sortType,
-		})
-
-	case models.FilterModeAssign:
-		// Get all issues assigned to this user.
-		issues, err = models.Issues(&models.IssuesOptions{
-			RepoID:     repoID,
-			AssigneeID: ctxUser.ID,
-			Page:       page,
-			IsClosed:   util.OptionalBoolOf(isShowClosed),
-			IsPull:     util.OptionalBoolOf(isPullList),
-			SortType:   sortType,
-		})
-
-	case models.FilterModeCreate:
-		// Get all issues created by this user.
-		issues, err = models.Issues(&models.IssuesOptions{
-			RepoID:   repoID,
-			PosterID: ctxUser.ID,
-			Page:     page,
-			IsClosed: util.OptionalBoolOf(isShowClosed),
-			IsPull:   util.OptionalBoolOf(isPullList),
-			SortType: sortType,
-		})
-	case models.FilterModeMention:
-		// Get all issues created by this user.
-		issues, err = models.Issues(&models.IssuesOptions{
-			RepoID:      repoID,
-			MentionedID: ctxUser.ID,
-			Page:        page,
-			IsClosed:    util.OptionalBoolOf(isShowClosed),
-			IsPull:      util.OptionalBoolOf(isPullList),
-			SortType:    sortType,
-		})
+	opts := &models.IssuesOptions{
+		RepoID:   repoID,
+		IsClosed: util.OptionalBoolOf(isShowClosed),
+		IsPull:   util.OptionalBoolOf(isPullList),
+		SortType: sortType,
 	}
 
+	switch filterMode {
+	case models.FilterModeAll:
+		opts.RepoIDs = userRepoIDs
+	case models.FilterModeAssign:
+		opts.AssigneeID = ctxUser.ID
+	case models.FilterModeCreate:
+		opts.PosterID = ctxUser.ID
+	case models.FilterModeMention:
+		opts.MentionedID = ctxUser.ID
+	}
+
+	counts, err := models.CountIssuesByRepo(opts)
+	if err != nil {
+		ctx.Handle(500, "CountIssuesByRepo", err)
+		return
+	}
+
+	opts.Page = page
+	opts.PageSize = setting.UI.IssuePagingNum
+	issues, err := models.Issues(opts)
 	if err != nil {
 		ctx.Handle(500, "Issues", err)
 		return
 	}
 
-	showRepos, err := models.IssueList(issues).LoadRepositories()
-	if err != nil {
-		ctx.Handle(500, "LoadRepositories", fmt.Errorf("%v", err))
-		return
+	showReposMap := make(map[int64]*models.Repository, len(counts))
+	for repoID := range counts {
+		repo, err := models.GetRepositoryByID(repoID)
+		if err != nil {
+			ctx.Handle(500, "GetRepositoryByID", err)
+			return
+		}
+		showReposMap[repoID] = repo
 	}
 
 	if repoID > 0 {
-		var theRepo *models.Repository
-		for _, repo := range showRepos {
-			if repo.ID == repoID {
-				theRepo = repo
-				break
-			}
-		}
-
-		if theRepo == nil {
-			theRepo, err = models.GetRepositoryByID(repoID)
+		if _, ok := showReposMap[repoID]; !ok {
+			repo, err := models.GetRepositoryByID(repoID)
 			if err != nil {
-				ctx.Handle(500, "GetRepositoryByID", fmt.Errorf("[#%d]%v", repoID, err))
+				ctx.Handle(500, "GetRepositoryByID", fmt.Errorf("[%d]%v", repoID, err))
 				return
 			}
-			showRepos = append(showRepos, theRepo)
+			showReposMap[repoID] = repo
 		}
 
+		repo := showReposMap[repoID]
+
 		// Check if user has access to given repository.
-		if !theRepo.IsOwnedBy(ctxUser.ID) && !theRepo.HasAccess(ctxUser) {
-			ctx.Handle(404, "Issues", fmt.Errorf("#%d", repoID))
+		if !repo.IsOwnedBy(ctxUser.ID) && !repo.HasAccess(ctxUser) {
+			ctx.Status(404)
 			return
 		}
 	}
 
-	err = models.RepositoryList(showRepos).LoadAttributes()
-	if err != nil {
+	showRepos := models.RepositoryListOfMap(showReposMap)
+	if err = showRepos.LoadAttributes(); err != nil {
 		ctx.Handle(500, "LoadAttributes", fmt.Errorf("%v", err))
 		return
 	}
 
+	for _, issue := range issues {
+		issue.Repo = showReposMap[issue.RepoID]
+	}
+
 	issueStats := models.GetUserIssueStats(repoID, ctxUser.ID, userRepoIDs, filterMode, isPullList)
 
 	var total int
@@ -369,6 +352,7 @@ func Issues(ctx *context.Context) {
 
 	ctx.Data["Issues"] = issues
 	ctx.Data["Repos"] = showRepos
+	ctx.Data["Counts"] = counts
 	ctx.Data["Page"] = paginater.New(total, setting.UI.IssuePagingNum, page, 5)
 	ctx.Data["IssueStats"] = issueStats
 	ctx.Data["ViewType"] = viewType
diff --git a/routers/user/home_test.go b/routers/user/home_test.go
new file mode 100644
index 000000000..beca93617
--- /dev/null
+++ b/routers/user/home_test.go
@@ -0,0 +1,33 @@
+// Copyright 2017 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package user
+
+import (
+	"net/http"
+	"testing"
+
+	"code.gitea.io/gitea/models"
+	"code.gitea.io/gitea/modules/test"
+
+	"code.gitea.io/gitea/modules/setting"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestIssues(t *testing.T) {
+	setting.UI.IssuePagingNum = 1
+	assert.NoError(t, models.LoadFixtures())
+
+	ctx := test.MockContext(t)
+	ctx.User = models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)
+	ctx.SetParams(":type", "issues")
+	ctx.Req.Form.Set("state", "closed")
+	Issues(ctx)
+	assert.EqualValues(t, http.StatusOK, ctx.Resp.Status())
+
+	assert.EqualValues(t, map[int64]int64{1: 1, 2: 1}, ctx.Data["Counts"])
+	assert.EqualValues(t, true, ctx.Data["IsShowClosed"])
+	assert.Len(t, ctx.Data["Issues"], 1)
+	assert.Len(t, ctx.Data["Repos"], 2)
+}
diff --git a/routers/user/main_test.go b/routers/user/main_test.go
new file mode 100644
index 000000000..83c0c6547
--- /dev/null
+++ b/routers/user/main_test.go
@@ -0,0 +1,33 @@
+// Copyright 2017 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package user
+
+import (
+	"fmt"
+	"os"
+	"path/filepath"
+	"testing"
+
+	"code.gitea.io/gitea/models"
+	"code.gitea.io/gitea/modules/setting"
+
+	_ "github.com/mattn/go-sqlite3" // for the test engine
+)
+
+func TestMain(m *testing.M) {
+	if err := models.CreateTestEngine("../../models/fixtures/"); err != nil {
+		fmt.Printf("Error creating test engine: %v\n", err)
+		os.Exit(1)
+	}
+
+	setting.AppURL = "https://try.gitea.io/"
+	setting.RunUser = "runuser"
+	setting.SSH.Port = 3000
+	setting.SSH.Domain = "try.gitea.io"
+	setting.RepoRootPath = filepath.Join(os.TempDir(), "repos")
+	setting.AppDataPath = filepath.Join(os.TempDir(), "appdata")
+
+	os.Exit(m.Run())
+}
diff --git a/templates/user/dashboard/issues.tmpl b/templates/user/dashboard/issues.tmpl
index a146250c1..954b86821 100644
--- a/templates/user/dashboard/issues.tmpl
+++ b/templates/user/dashboard/issues.tmpl
@@ -23,7 +23,7 @@
 					{{range .Repos}}
 						<a class="{{if eq $.RepoID .ID}}ui basic blue button{{end}} repo name item" href="{{$.Link}}?type={{$.ViewType}}{{if not (eq $.RepoID .ID)}}&repo={{.ID}}{{end}}&sort={{$.SortType}}&state={{$.State}}">
 							<span class="text truncate">{{.FullName}}</span>
-							<div class="floating ui {{if $.IsShowClosed}}red{{else}}green{{end}} label">{{if $.IsShowClosed}}{{if $.PageIsPulls}}{{.NumClosedPulls}}{{else}}{{.NumClosedIssues}}{{end}}{{else}}{{if $.PageIsPulls}}{{.NumOpenPulls}}{{else}}{{.NumOpenIssues}}{{end}}{{end}}</div>
+							<div class="floating ui {{if $.IsShowClosed}}red{{else}}green{{end}} label">{{index $.Counts .ID}}</div>
 						</a>
 					{{end}}
 				</div>