Contents

Customize Hugo LoveIt Theme Pagination: Previous/Next Controls and Page Jump

Hugo LoveIt comes with a built-in Pagination feature that allows users to navigate between different pages using page numbers on the homepage. This prevents all posts from being displayed on a single page and can also help improve the website’s loading speed.

The original pagination navigation is primarily based on page numbers, which is generally sufficient for everyday browsing. However, as the website’s content gradually grows and the number of pages increases, it becomes impractical to display every page number in the navigation bar. Relying solely on page numbers can therefore make navigation somewhat inconvenient.

For this reason, I decided to make a small modification to LoveIt’s pagination navigation. While keeping the original page-number navigation, I added Previous Page, Next Page, and page number jump functionality, making pagination more convenient and complete.

Why Make This Modification

LoveIt’s original Pagination feature mainly provides page-number navigation, for example:

1
1 ... 4 5 6 ... 10

When there are many pages, the page numbers are simplified using .... However, if you want to continuously browse to the previous or next page, you still have to manually find the corresponding page number, which can be slightly inconvenient.

Therefore, while keeping the original page-number navigation, I added Previous Page, Next Page, and page number jump functionality to make pagination more intuitive and convenient.

On desktop, these functions are placed on the same row, allowing the previous page, page numbers, next page, and page jump features to be accessed directly:

1
◀︎  1 ... 4 5 6 ... 10  ▶︎  [Page] [Go]

On mobile devices, the page numbers and other controls are divided into two rows to prevent the interface from becoming too crowded:

1
2
1 ... 4 5 6 ... 10
◀︎  [Page] [Go]  ▶︎

Modified File Locations

The LoveIt theme’s Pagination template modified in this customization is located at layouts/partials/paginator.html. In addition, the CSS and multilingual text will also be adjusted in the following files:

1
2
3
4
5
6
CSS:
assets\css\_custom.scss

i18n:
i18n\zh-TW.toml
i18n\en.toml

Before Modification

The original layouts\partials\paginator.html only provided basic page-number pagination functionality:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{{- if gt .Paginator.TotalPages 1 -}}
    <ul class="pagination">
        {{- .Scratch.Set "paginator.ellipsed" false -}}
        {{- range $.Paginator.Pagers -}}
            {{- $right := sub .TotalPages .PageNumber -}}
            {{- $showNumber := or (le .PageNumber 1) (eq $right 0) -}}
            {{- $showNumber := or $showNumber (and (gt .PageNumber (sub $.Paginator.PageNumber 3)) (lt .PageNumber (add $.Paginator.PageNumber 3))) -}}
            {{- if $showNumber -}}
                {{- $.Scratch.Set "paginator.ellipsed" false -}}
                {{- $.Scratch.Set "paginator.shouldEllipse" false -}}
            {{- else -}}
                {{- $.Scratch.Set "paginator.shouldEllipse" (not ($.Scratch.Get "paginator.ellipsed") ) -}}
                {{- $.Scratch.Set "paginator.ellipsed" true -}}
            {{- end -}}
            {{- if $showNumber -}}
                <li class="page-item {{ if eq . $.Paginator }}active{{ end }}">
                    <span class="page-link">
                        <a href="{{ .URL }}">
                            {{- .PageNumber -}}
                        </a>
                    </span>
                </li>
            {{- else if ($.Scratch.Get "paginator.shouldEllipse") -}}
                <li class="page-item ">
                    <span class="page-link" aria-hidden="true">&hellip;</span>
                </li>
            {{- end -}}
        {{- end -}}
    </ul>
{{- end -}}

After Modification

The modified layouts\partials\paginator.html:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
{{- if gt .Paginator.TotalPages 1 -}}
    <ul class="pagination">
        {{/* Previous */}}
        {{- if .Paginator.HasPrev -}}
            <li class="page-item pagination-prev">
                <a class="page-link" href="{{ .Paginator.Prev.URL }}" aria-label="{{ i18n "pagination.previous" }}" title="{{ i18n "pagination.previous" }}">
                    {{ i18n "pagination.previousSymbol" }}
                </a>
            </li>
        {{- else -}}
            <li class="page-item pagination-prev disabled">
                <a class="page-link" href="javascript:void(0);" aria-label="{{ i18n "pagination.previous" }}" title="{{ i18n "pagination.previous" }}" onclick="alert('{{ i18n "pagination.firstPage" }}');">
                    {{ i18n "pagination.previousSymbol" }}
                </a>
            </li>
        {{- end -}}

        {{/* Pages */}}
        <li class="pagination-pages">
            <ul class="pagination-pages-list">
                {{- .Scratch.Set "paginator.ellipsed" false -}}
                {{- range $.Paginator.Pagers -}}
                    {{- $right := sub .TotalPages .PageNumber -}}
                    {{- $showNumber := or (le .PageNumber 1) (eq $right 0) -}}
                    {{- $showNumber := or $showNumber (and (gt .PageNumber (sub $.Paginator.PageNumber 3)) (lt .PageNumber (add $.Paginator.PageNumber 3))) -}}
                    {{- if $showNumber -}}
                        {{- $.Scratch.Set "paginator.ellipsed" false -}}
                        {{- $.Scratch.Set "paginator.shouldEllipse" false -}}
                    {{- else -}}
                        {{- $.Scratch.Set "paginator.shouldEllipse" (not ($.Scratch.Get "paginator.ellipsed") ) -}}
                        {{- $.Scratch.Set "paginator.ellipsed" true -}}
                    {{- end -}}
                    {{- if $showNumber -}}
                        <li class="page-item pagination-page {{ if eq . $.Paginator }}active{{ end }}">
                            <span class="page-link">
                                <a href="{{ .URL }}" title="{{ i18n "pagination.page" . }}" aria-label="{{ i18n "pagination.page" . }}">
                                    {{- .PageNumber -}}
                                </a>
                            </span>
                        </li>
                    {{- else if ($.Scratch.Get "paginator.shouldEllipse") -}}
                        <li class="page-item pagination-page">
                            <span class="page-link" aria-hidden="true">&hellip;</span>
                        </li>
                    {{- end -}}
                {{- end -}}
            </ul>
        </li>

        {{/* Next */}}
        {{- if .Paginator.HasNext -}}
            <li class="page-item pagination-next">
                <a class="page-link" href="{{ .Paginator.Next.URL }}" aria-label="{{ i18n "pagination.next" }}" title="{{ i18n "pagination.next" }}">
                    {{ i18n "pagination.nextSymbol" }}
                </a>
            </li>
        {{- else -}}
            <li class="page-item pagination-next disabled">
                <a class="page-link" href="javascript:void(0);" aria-label="{{ i18n "pagination.next" }}" title="{{ i18n "pagination.next" }}" onclick="alert('{{ i18n "pagination.lastPage" }}');">
                    {{ i18n "pagination.nextSymbol" }}
                </a>
            </li>
        {{- end -}}

        {{/* Page Jump */}}
        <li class="page-item page-jump">
            <form
                class="page-jump-form"
                onsubmit="return jumpToPage(this);"
            >
                <input
                    type="number"
                    name="page"
                    min="1"
                    max="{{ .Paginator.TotalPages }}"
                    placeholder="{{ i18n "pagination.pagePlaceholder" }}"
                    aria-label="{{ i18n "pagination.pagePlaceholder" }}"
                    title="{{ i18n "pagination.pagePlaceholder" }}"
                    inputmode="numeric"
                    required
                >

                <button type="submit" title="{{ i18n "pagination.jump" }}">
                    {{ i18n "pagination.jump" }}
                </button>
            </form>
        </li>
    </ul>

{{/* 頁數跳轉 JavaScript */}}
<script>
    function jumpToPage(form) {
        const input = form.querySelector('input[name="page"]');
        const page = parseInt(input.value, 10);
        const totalPages = {{ .Paginator.TotalPages }};

        if (
            isNaN(page) ||
            page < 1 ||
            page > totalPages
        ) {
            input.reportValidity();
            return false;
        }
        const baseURL = "{{ .Paginator.First.URL }}";
        if (page === 1) {
            window.location.href = baseURL;
        } else {
            const url = new URL(
                baseURL,
                window.location.origin
            );

            if (url.pathname === '/') {
                url.pathname = '/page/' + page + '/';
            } else {
                url.pathname = url.pathname.replace(/\/+$/, '') + '/page/' + page + '/';
            }
            window.location.href = url.toString();
        }
        return false;
    }
</script>
{{- end -}}

Added CSS

Add the following CSS to the website’s stylesheet:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
/* 移除分頁左右空白 */
.pagination {
    margin-left: 0;
    margin-right: 0;
    padding-left: 0;
    padding-right: 0;
}

/* 頁碼群組不參與外層動畫 */
.pagination .pagination-pages {
    margin: 0;
    padding: 0;

    &:before,
    &:after {
        display: none;
    }

    &:hover {
        &:before,
        &:after {
            width: 0;
        }
    }
}

/* 頁碼群組停用父層 hover 文字效果 */
.pagination .pagination-pages:hover a {
    color: #bfbfbf;
}

/* 頁碼個別元素保留 hover 效果 */
.pagination-page {
    &:hover a {
        color: $pagination-link-hover-color !important;
    }

    &.active a {
        color: $pagination-link-hover-color !important;
    }
}

/* 深色模式頁碼 hover 與啟用效果 */
[theme=dark] .pagination-page:hover a,
[theme=dark] .pagination-page.active a {
    color: $pagination-link-hover-color-dark !important;
}

/* 顯示無法使用的上一頁與下一頁 */
.pagination .pagination-prev.disabled,
.pagination .pagination-next.disabled {
    display: inline;
    color: #bfbfbf;
}

/* 頁碼列表使用彈性排列 */
.pagination-pages-list {
    display: flex;
    align-items: center;
    margin: 0;
    padding: 0;
    list-style: none;
}

/* 分頁左右按鈕與頁碼群組保持緊密排列 */
.pagination-prev,
.pagination-pages,
.pagination-next {
    flex: 0 0 auto;
}

.pagination-prev {
    margin-right: 0.25em !important;
    font-size: 1rem;
    a {
        font-size: 1rem;
    }
}

.pagination-next {
    margin-left: 0.1em !important;
    font-size: 1rem;
    a {
        font-size: 1rem;
    }
}

/* 手機版分頁改為上下兩行 */
@media (max-width: 479px) {
    /* 分頁容器允許換行並置中 */
    .pagination {
        display: flex;
        flex-wrap: wrap;
        justify-content: center;
    }

    /* 第一行顯示頁碼 */
    .pagination-pages {
        order: 1;
        flex: 0 0 100%;
        width: 100%;
        display: flex;
        justify-content: center;
        margin-bottom: 0.75em !important;
    }

    /* 手機版頁碼保持置中並縮小間距 */
    .pagination-pages-list {
        justify-content: center;

        .pagination-page {
            margin-left: 0.9em !important;
            margin-right: 0.9em !important;
        }
    }

    /* 第二行依序排列上一頁、跳頁、下一頁 */
    .pagination-prev {
        order: 2;
    }

    .page-jump {
        order: 3;
    }

    .pagination-next {
        order: 4;
    }
}

/* 輸入頁數跳轉元件 */
.page-jump {
    margin-left: 0.4em !important;
    margin-right: 0.4em !important;
}

.page-jump-form {
    display: inline-flex;
    align-items: center;
    gap: 0.35em;
    margin: 0;
    padding: 0;
}

/* 頁數輸入框基本樣式 */
.page-jump-form input {
    width: 5em;
    height: 2.2em;
    box-sizing: border-box;
    padding: 0 0.5em;
    border: 1px solid rgba(128, 128, 128, 0.35);
    border-radius: 0.5em;
    background: transparent;
    color: inherit;
    font: inherit;
    font-size: 0.8rem;
    text-align: center;
    outline: none;
    transition:
        border-color 0.2s ease,
        box-shadow 0.2s ease;
}

/* 頁數輸入框滑入與聚焦效果 */
.page-jump-form input:hover {
    border-color: rgba(128, 128, 128, 0.6);
}

.page-jump-form input:focus {
    border-color: $pagination-link-hover-color;
    box-shadow: 0 0 0 0.15em rgba(128, 128, 128, 0.12);
}

/* 頁數輸入框提示文字 */
.page-jump-form input::placeholder {
    color: inherit;
    opacity: 0.5;
}

/* 跳轉按鈕基本樣式 */
.page-jump-form button {
    height: 2.2em;
    padding: 0 0.7em;
    border: 1px solid $pagination-link-hover-color;
    border-radius: 0.5em;
    background: $pagination-link-hover-color;
    color: #fff;
    font: inherit;
    font-size: 0.8rem;
    font-weight: 600;
    cursor: pointer;
    transition:
        opacity 0.2s ease,
        transform 0.15s ease,
        box-shadow 0.2s ease;
}

/* 跳轉按鈕滑入與按下效果 */
.page-jump-form button:hover {
    opacity: 0.9;
    box-shadow: 0 0.2em 0.5em rgba(0, 0, 0, 0.15);
}

.page-jump-form button:active {
    transform: translateY(0.05em);
    box-shadow: none;
}

/* 深色模式輸入框樣式 */
[theme=dark] .page-jump-form input {
    background: rgba(14, 13, 13, 0.9);
    color: #fff;
    border-color: rgba(255, 255, 255, 0.2);
    box-shadow: 0 0.25em 0.6em rgba(0, 0, 0, 0.3);
}

/* 深色模式輸入框滑入與聚焦效果 */
[theme=dark] .page-jump-form input:hover {
    border-color: rgba(255, 255, 255, 0.4);
}

[theme=dark] .page-jump-form input:focus {
    border-color: $pagination-link-hover-color-dark;
    box-shadow: 0 0 0 0.15em rgba(255, 255, 255, 0.1);
}

/* 深色模式跳轉按鈕樣式 */
[theme=dark] .page-jump-form button {
    border-color: $pagination-link-hover-color-dark;
    color: #fff;
}

/* 手機版縮小跳頁元件 */
@media (max-width: 479px) {
    .page-jump {
        margin-left: 0.3em !important;
        margin-right: 0.3em !important;
    }

    .page-jump-form {
        gap: 0.25em;
    }

    .page-jump-form input {
        width: 4em;
        height: 2.1em;
    }

    .page-jump-form button {
        height: 2.1em;
        padding: 0 0.65em;
    }
}

Added i18n

i18n\zh-TW.toml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[pagination.previous]
other = "上一頁"

[pagination.previousSymbol]
other = "◀︎"

[pagination.firstPage]
other = "已經到最前面了哦!"

[pagination.next]
other = "下一頁"

[pagination.nextSymbol]
other = "▶︎"

[pagination.lastPage]
other = "這是最後一頁了!"

[pagination.page]
other = "第 {{ .PageNumber }} 頁"

[pagination.pagePlaceholder]
other = "頁數"

[pagination.jump]
other = "前往"

i18n\en.toml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[pagination.previous]
other = "Previous"

[pagination.previousSymbol]
other = "◀︎"

[pagination.firstPage]
other = "You're already at the very first page!"

[pagination.next]
other = "Next"

[pagination.nextSymbol]
other = "▶︎"

[pagination.lastPage]
other = "You've reached the very last page!"

[pagination.page]
other = "Page {{ .PageNumber }}"

[pagination.pagePlaceholder]
other = "Page"

[pagination.jump]
other = "Go"

Conclusion

This modification keeps LoveIt’s original pagination functionality while adding Previous Page, Next Page, and page number jump features, making pagination more convenient when the website contains a large number of posts. In addition, i18n is used to support multilingual text switching, while the layout is adjusted separately for desktop and mobile devices to ensure a good user experience across different screen sizes.

Overall, these changes do not require any modifications to Hugo’s core functionality. By simply adjusting the Pagination template, CSS, and i18n, the original pagination navigation can be made more complete.

Environment

  • Hugo 0.145.0
  • LoveIt theme (version available on GitHub as of February 21, 2025)