Skip to content

Commit 31f6194

Browse files
authored
feat(search): use useRouter from Next.js instead of history API (#157)
2 parents da3685c + 11f32c5 commit 31f6194

23 files changed

+227
-204
lines changed

.github/workflows/auto-release.yml

+3-6
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ on:
44
workflow_dispatch:
55
inputs:
66
tag:
7-
description: "Tag name for release (leave empty to use latest pushed tag)"
7+
description: Tag name for release (leave empty to use latest pushed tag)
88
required: false
99
push:
1010
tags:
11-
- "v*.*.*" # Match only version-like tags (e.g., v1.0.0, v2.1.3)
11+
- 'v*.*.*' # Match only version-like tags (e.g., v1.0.0, v2.1.3)
1212

1313
permissions:
1414
contents: write # Required for GitHub release creation
@@ -67,11 +67,8 @@ jobs:
6767
📢 **SuzuBlog new version released!** 🚀
6868
新版本发布!请查看自动生成的 Release Notes 🔽
6969
70-
## What's Changed
71-
- Automatically generated Release Notes based on changes in this release.
72-
7370
## Full Changelog
74-
[View Full Changelog](https://github.com/ZL-Asica/SuzuBlog/compare/${{ env.LAST_TAG }}...${{ env.TAG_NAME }})
71+
[View Full Changelog](https://github.com/ZL-Asica/SuzuBlog/blob/main/CHANGELOG.md)
7572
7673
## Contributors
7774
🏆 Thank you to everyone who contributed to this release!

CHANGELOG.md

+25
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,30 @@
11
# SuzuBlog Changelog
22

3+
## 1.7.1 (2025-04-12)
4+
5+
### Patch Changes
6+
7+
- Switch to useRouter for pagination and search
8+
9+
- feat(search): use useRouter from Next.js instead of history API
10+
11+
- Introduce `useUpdateURL` hook to manage URL updates.
12+
- Replace `history.` with `useRouter` for better integration with Next.js.
13+
- With `useRouter`, fix the scroll behavior when current page changes.
14+
- Add sanitize query logic to prevent XSS attacks (before only from URL, now also from input).
15+
- Extract `SearchInput` component from `PostPageClient` to `src/app/posts/page.tsx` for better organization and performance (categories and tags array also generated without re-rendering).
16+
17+
- chore: some updates on minor details
18+
19+
- Finally remove `tailwind.config.ts` from README (since from v4.0.0 it has been removed)
20+
- Update manifest theme color.
21+
- Fix thumbnail showing when parameter `showThumbnail` is set to `false`.
22+
- Replace some a tags with NextLink.
23+
24+
- style(search): extract select in search out
25+
- Color adjusted a little bit. Now the color is different in light and dark mode.
26+
- Added a new component Select to be used in the search input.
27+
328
## 1.7.0 (2025-04-11)
429

530
### Minor Changes

README.md

+5-2
Original file line numberDiff line numberDiff line change
@@ -41,19 +41,22 @@ For setup, configuration, Markdown syntax, and deployment guides, follow the doc
4141
│ ├── components # Reusable components
4242
│ ├── services # Logic for content parsing, configuration, etc.
4343
│ └── types.d.ts # Global type definitions
44-
├── tailwind.config.ts # Tailwind CSS configuration
4544
├── package.json # Project dependencies and scripts
4645
└── pnpm-lock.yaml # pnpm dependency lock file
4746
```
4847

4948
## ❤️ About Suzu
5049

51-
After years of frustration with the maintenance, security risks, and performance issues of other frameworks, I decided to create Suzu Blog using **Next.js**. It is simple, efficient, and highly customizable, designed for anyone looking to build a modern blog quickly.
50+
After years of frustration with the maintenance, security risks, and performance issues of other frameworks, I decided to create Suzu Blog using **Next.js**. It is simple, efficient, and highly customizable, designed for anyone looking to build a modern blog quickly. If you enjoy using it, please consider giving it a star! ⭐ I hope you find it as enjoyable as I do!
5251

5352
## 🔗 Community Support
5453

5554
**Contribute**: Contributions are welcome! Please refer to the [Contribution Guide](./CONTRIBUTING.md).
5655

56+
## 📜 License
57+
58+
This project is licensed under the [AGPL-3.0 License][license-link]. See the [LICENSE](./LICENSE) file for details.
59+
5760
<!-- Badges / Links -->
5861

5962
[eslint-badge]: https://img.shields.io/badge/eslint-4B32C3?logo=eslint&logoColor=white

README_JA.md

+5-2
Original file line numberDiff line numberDiff line change
@@ -41,19 +41,22 @@ Suzu Blog のセットアップ、設定、Markdown の書き方、デプロイ
4141
│ ├── components # 再利用可能なコンポーネント
4242
│ ├── services # コンテンツ解析、設定処理などのロジック
4343
│ └── types.d.ts # グローバル型定義
44-
├── tailwind.config.ts # Tailwind CSS 設定
4544
├── package.json # プロジェクト依存関係とスクリプト
4645
└── pnpm-lock.yaml # pnpm 依存関係ロックファイル
4746
```
4847

4948
## ❤️ Suzu について
5049

51-
長年にわたり、さまざまなブログフレームワークを試してきましたが、**保守の手間・セキュリティリスク・パフォーマンス問題** に悩まされてきました。そこで、私は **Next.js** を用いて **シンプル・高効率・カスタマイズ性抜群** の Suzu Blog を開発しました。モダンなブログを素早く構築したいすべての人のためのテンプレートです。
50+
長年にわたり、さまざまなブログフレームワークを試してきましたが、**保守の手間・セキュリティリスク・パフォーマンス問題** に悩まされてきました。そこで、私は **Next.js** を用いて **シンプル・高効率・カスタマイズ性抜群** の Suzu Blog を開発しました。モダンなブログを素早く構築したいすべての人のためのテンプレートです。もしこのプロジェクトが気に入ったら、ぜひ ⭐ を付けてください!私と同じように楽しんでいただければ幸いです!
5251

5352
## 🔗 コミュニティサポート
5453

5554
**貢献**: 貢献を歓迎します!詳しくは [Contribution Guide](./CONTRIBUTING.md) を参照してください。
5655

56+
## 📜 ライセンス
57+
58+
本プロジェクトは [AGPL-3.0 ライセンス][license-link] の下でライセンスされています。詳細については [LICENSE](./LICENSE) ファイルをご覧ください。
59+
5760
<!-- Badges / Links -->
5861

5962
[eslint-badge]: https://img.shields.io/badge/eslint-4B32C3?logo=eslint&logoColor=white

README_ZH.md

+5-2
Original file line numberDiff line numberDiff line change
@@ -41,19 +41,22 @@ Suzu Blog 的安装、配置、Markdown 语法、部署等详细教程,请参
4141
│ ├── components # 复用组件
4242
│ ├── services # 服务逻辑(内容解析、配置加载等)
4343
│ └── types.d.ts # 全局类型定义
44-
├── tailwind.config.ts # Tailwind CSS 配置
4544
├── package.json # 项目依赖与脚本
4645
└── pnpm-lock.yaml # pnpm 依赖锁定
4746
```
4847

4948
## ❤️ 关于 Suzu
5049

51-
在多年使用各种博客框架的过程中,我深受 **维护成本高、安全隐患多、性能不稳定** 等问题的困扰。最终,我决定基于 **Next.js** 打造 Suzu Blog —— 一个 **简洁、高效、可高度自定义** 的博客模板,帮助任何人快速搭建现代化博客。
50+
在多年使用各种博客框架的过程中,我深受 **维护成本高、安全隐患多、性能不稳定** 等问题的困扰。最终,我决定基于 **Next.js** 打造 Suzu Blog —— 一个 **简洁、高效、可高度自定义** 的博客模板,帮助任何人快速搭建现代化博客。如果你喜欢这个项目,请考虑给它一个 ⭐!我希望你能和我一样享受使用它的乐趣!
5251

5352
## 🔗 社区支持
5453

5554
**贡献**:欢迎提出问题或贡献代码!请访问 [贡献指南](https://github.com/ZL-Asica/SuzuBlog/blob/main/CONTRIBUTING.md)
5655

56+
## 📜 许可证
57+
58+
本项目遵循 [AGPL-3.0 许可证][license-link]。有关详细信息,请参阅 [LICENSE](./LICENSE) 文件。
59+
5760
<!-- Badges / Links -->
5861

5962
[eslint-badge]: https://img.shields.io/badge/eslint-4B32C3?logo=eslint&logoColor=white

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "suzu-blog",
3-
"version": "1.7.0",
3+
"version": "1.7.1",
44
"private": true,
55
"packageManager": "[email protected]",
66
"author": {

src/app/[slug]/page.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -51,15 +51,15 @@ export async function generateMetadata({ params }: Properties): Promise<Metadata
5151
modifiedTime: postData?.frontmatter.date,
5252
title: postData?.frontmatter.title ?? config.title ?? 'Default Title',
5353
description: postData?.postAbstract ?? config.description ?? 'Default description',
54-
images: postData?.frontmatter.thumbnail,
54+
images: postData?.frontmatter.showThumbnail !== false ? postData?.frontmatter.thumbnail : undefined,
5555
url: `/${slug}`,
5656
locale: config.lang,
5757
},
5858
twitter: {
5959
card: 'summary',
6060
title: postData?.frontmatter.title ?? config.title ?? 'Default Title',
6161
description: postData?.postAbstract ?? config.description ?? 'Default description',
62-
images: postData?.frontmatter.thumbnail,
62+
images: postData?.frontmatter.showThumbnail !== false ? postData?.frontmatter.thumbnail : undefined,
6363
},
6464
}
6565
}
@@ -95,7 +95,7 @@ export default async function PostPage(props: { params: Promise<{ slug: string }
9595
'@type': 'WebPage',
9696
'@id': `${config.siteUrl}/${post.slug}`,
9797
},
98-
'image': post?.frontmatter.thumbnail,
98+
'image': post?.frontmatter.showThumbnail !== false ? post?.frontmatter.thumbnail : undefined,
9999
'publisher': {
100100
'@type': 'Organization',
101101
'name': config.title,

src/app/layout.tsx

+1-6
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
11
import type { Metadata } from 'next'
2-
import BackToTop from '@/components/common/BackToTop'
3-
import Footer from '@/components/common/Footer'
4-
import Header from '@/components/common/Header'
5-
import ScrollPositionBar from '@/components/common/ScrollPositionBar'
2+
import { BackToTop, Footer, Header, ScrollPositionBar } from '@/components/common'
63
import { getConfig } from '@/services/config'
74
import { Analytics } from '@vercel/analytics/react'
8-
95
import { SpeedInsights } from '@vercel/speed-insights/next'
10-
116
import { Inter, JetBrains_Mono, Noto_Sans_SC } from 'next/font/google'
127
import Script from 'next/script'
138

src/app/manifest.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ function manifest(): MetadataRoute.Manifest {
1212
start_url: '/',
1313
display: 'standalone',
1414
background_color: '#ffffff',
15-
theme_color: '#f6a8b8',
15+
theme_color: '#ff9fb2',
1616
lang: config.lang,
1717
orientation: 'any',
1818
icons: [

src/app/posts/page.tsx

+13-7
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import type { Metadata } from 'next'
22
import PostsPageClient from '@/components/posts/PostPageClient'
3-
3+
import SearchInput from '@/components/posts/SearchInput'
44
import { getConfig } from '@/services/config'
55
import { getAllPosts } from '@/services/content'
6-
76
import Head from 'next/head'
87

98
export function generateMetadata(): Metadata {
@@ -49,6 +48,9 @@ export default async function PostsPage() {
4948
},
5049
}
5150

51+
const categories = Array.from(new Set(posts.flatMap(post => post.frontmatter.categories || [])))
52+
const tags = Array.from(new Set(posts.flatMap(post => post.frontmatter.tags || [])))
53+
5254
return (
5355
<>
5456
<Head>
@@ -57,11 +59,15 @@ export default async function PostsPage() {
5759
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
5860
/>
5961
</Head>
60-
<PostsPageClient
61-
posts={posts}
62-
translation={translation}
63-
postsPerPage={Math.min(15, config.postsPerPage ?? 5)}
64-
/>
62+
<div className="container mt-5 mx-auto flex flex-col items-center p-4">
63+
<SearchInput categories={categories} tags={tags} translation={translation} />
64+
65+
<PostsPageClient
66+
posts={posts}
67+
translation={translation}
68+
postsPerPage={Math.min(15, config.postsPerPage ?? 5)}
69+
/>
70+
</div>
6571
</>
6672
)
6773
}

src/components/article/CategoriesTagsList.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use client'
22

33
import { Folder, Tags } from 'lucide-react'
4+
import Link from 'next/link'
45
import { useSearchParams } from 'next/navigation'
56

67
interface CategoriesTagsListProps {
@@ -50,15 +51,15 @@ const CategoriesTagsList = ({ type, translation, items = defaultItems }: Categor
5051
<span>
5152
{links.map(({ item, href }, index) => (
5253
<span key={item}>
53-
<a
54+
<Link
5455
href={href}
5556
target="_self"
5657
title={`${translation.navigate} ${typeTranslation} ${item}`}
5758
aria-label={`${translation.navigate} ${typeTranslation} ${item}`}
5859
className="text-hover-primary transition-all-300 font-medium hover:underline hover:decoration-dotted hover:underline-offset-4"
5960
>
6061
{item}
61-
</a>
62+
</Link>
6263
{/* Add comma, except for the last item */}
6364
{index < items.length - 1 && ', '}
6465
</span>

src/components/common/Footer.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ const Footer = ({ config }: FooterProps) => {
4444
by
4545
{' '}
4646
<Link
47-
href="https://www.zla.pub"
47+
href="https://zla.pub"
4848
target="_blank"
4949
aria-label="ZL Asica's blog (new tab)"
5050
rel="noopener noreferrer"

src/components/posts/PostList.tsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -55,27 +55,27 @@ const PostList = ({ posts, translation }: PostListProps) => {
5555
<span className="text-sm font-medium">{post.frontmatter.date.split(' ')[0]}</span>
5656
</div>
5757
{/* Title in Frontmatter */}
58-
<a
58+
<Link
5959
href={postLink}
6060
target="_self"
6161
aria-label={`${translation.post.readMore} ${postTitle}`}
6262
className="text-hover-primary transition-colors-500"
6363
>
6464
<h2 className="mb-2 text-2xl font-bold">{postTitle}</h2>
65-
</a>
65+
</Link>
6666
{/* Abstract */}
6767
<p className="line-clamp-5 text-sm">{post.postAbstract}</p>
6868
</div>
6969

7070
<div className="text-gray-450 mt-3 flex items-center justify-between text-sm">
71-
<a
71+
<Link
7272
href={postLink}
7373
target="_self"
7474
aria-label={`${postTitle}`}
7575
className="self-start text-hover-primary transition-all-500 hover:scale-110"
7676
>
7777
<Ellipsis size={32} strokeWidth={3} className="cursor-pointer" />
78-
</a>
78+
</Link>
7979

8080
{/* Category */}
8181
<CategoriesTagsList

src/components/posts/PostPageClient.tsx

+11-37
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
11
'use client'
22

3-
import { useSearchedPosts } from '@/hooks'
4-
import { updateURL } from '@/services/utils'
5-
3+
import { useSearchedPosts, useUpdateURL } from '@/hooks'
64
import { backToTop } from '@zl-asica/react'
75
import { useSearchParams } from 'next/navigation'
86
import { useMemo } from 'react'
9-
107
import Pagination from './Pagination'
118
import PostListLayout from './PostList'
12-
import SearchInput from './SearchInput'
139

1410
interface PostPageClientProps {
1511
posts: PostListData[]
@@ -22,50 +18,28 @@ const PostPageClient = ({
2218
translation,
2319
postsPerPage,
2420
}: PostPageClientProps) => {
25-
const searchParameters = useSearchParams()
26-
const queryParameters = searchParameters.get('query') ?? ''
27-
const categoryParameter = searchParameters.get('category') ?? ''
28-
const tagParameter = searchParameters.get('tag') ?? ''
21+
const updateURL = useUpdateURL()
22+
const searchParams = useSearchParams()
2923

30-
const searchQueries = {
31-
query: queryParameters,
32-
category: categoryParameter,
33-
tag: tagParameter,
34-
}
35-
const filteredPosts = useSearchedPosts(posts, searchQueries)
24+
const filteredPosts = useSearchedPosts(posts, searchParams)
3625

3726
const currentPage = useMemo(() => {
38-
const page = Number(searchParameters.get('page') ?? '1')
27+
const page = Number(searchParams.get('page') ?? '1')
3928
return Number.isNaN(page) || page < 1 ? 1 : page
40-
}, [searchParameters])
29+
}, [searchParams])
4130

42-
const handlePageChange = (page: number) => {
31+
const handleCurrentPageChange = (page: number) => {
4332
backToTop(10)()
44-
updateURL({ page }, { replace: false })
33+
updateURL({ page })
4534
}
4635

47-
const categories = useMemo(() => [
48-
...new Set(posts.flatMap(post => post.frontmatter.categories || [])),
49-
], [posts])
50-
const tags = useMemo(() => [
51-
...new Set(posts.flatMap(post => post.frontmatter.tags || [])),
52-
], [posts])
53-
5436
const currentPosts = filteredPosts.slice(
5537
(currentPage - 1) * postsPerPage,
5638
currentPage * postsPerPage,
5739
)
5840

5941
return (
60-
<div className="container mt-5 mx-auto flex flex-col items-center p-4">
61-
{/* Centered Search Input */}
62-
<SearchInput
63-
categories={categories}
64-
tags={tags}
65-
translation={translation}
66-
searchQueries={searchQueries}
67-
/>
68-
42+
<>
6943
{/* Post List */}
7044
{filteredPosts.length === 0 && (
7145
<h2 className="my-4 text-3xl font-bold">
@@ -80,9 +54,9 @@ const PostPageClient = ({
8054
postsPerPage={postsPerPage}
8155
totalPosts={filteredPosts.length}
8256
currentPage={currentPage}
83-
setCurrentPage={handlePageChange}
57+
setCurrentPage={handleCurrentPageChange}
8458
/>
85-
</div>
59+
</>
8660
)
8761
}
8862

0 commit comments

Comments
 (0)