🌊 一、序章:当列表无限,服务器汗流浃背
想象你在做一个博客网站,用 Next.js 写 API,数据库里有十万篇文章。
如果你一次性在 SSR 阶段把全部文章 fetch
回来,那服务器会用一种哲学的口吻警告你:
"孩子,分页是对性能的尊重。"
于是,当数据塞满栈顶,我们学会了分页(Pagination)。
接着,用户又提出新需求:
"我想模糊搜一下标题里含'GPT'的文章。"
于是我们又召唤了模糊搜索(Fuzzy Search)。
这两个家伙一结合,就成了 Web 中"可伸缩的数据接口黄金搭档"。
📦 二、分页的本质:切片,送到用户嘴边 🍰
分页不只是"每页10条"的UI技巧,而是一次数据切片的哲学动作。
在底层,它往往体现为这样的流程:
arduino
Client → API Route → DB Query (limit, offset)
│
▼
返回特定数据集
分页的核心参数:
参数 | 含义 | 示例 |
---|---|---|
page |
当前页码 | 1, 2, 3, ... |
limit |
每页数据条数 | 10 |
offset |
数据偏移量 = (page - 1) × limit | 20 |
⚙️ 三、在 Next.js 中优雅地分页
我们可以使用 App Router + Prisma 作为例子。
js
// /app/api/posts/route.js
import { prisma } from "@/lib/prisma";
export async function GET(req) {
const { searchParams } = new URL(req.url);
const page = parseInt(searchParams.get("page")) || 1;
const limit = parseInt(searchParams.get("limit")) || 10;
const skip = (page - 1) * limit;
const posts = await prisma.post.findMany({
skip,
take: limit,
orderBy: { createdAt: "desc" },
});
const total = await prisma.post.count();
return Response.json({
data: posts,
meta: {
page,
limit,
total,
totalPages: Math.ceil(total / limit),
},
});
}
💡 注释点评:
skip
控制从第几条数据开始取;take
控制一次抓多少条;orderBy
保证翻页的排序稳定(别让同一篇文章在前后页间乱跳 🧨)。
📖 小图例:分页请求生命周期
sql
+-----------------+
| 用户点击下一页 ▶ |
+--------┬--------+
│ fetch /api/posts?page=2
▼
+-----------------------+
| Next.js Route Handler |
+--------┬--------------+
│ Prisma Query (skip=10, take=10)
▼
+-----------------------+
| 返回JSON响应 |
+-----------------------+
"分页不是切割,而是节奏。"
------ 一位数据库诗人
🔍 四、模糊搜索:让数据库懂点"人话"
搜索的本质是信息检索(Information Retrieval)。
模糊搜索就是在告诉数据库:
"别太死板,我记不太清楚关键字了,你帮我猜猜?"
在 SQL 环境中,它通常基于 LIKE
关键字实现(或更高级的 Full Text Index
)。
案例:Next.js + Prisma 实现模糊搜索
php
// /app/api/posts/search/route.js
import { prisma } from "@/lib/prisma";
export async function GET(req) {
const { searchParams } = new URL(req.url);
const q = searchParams.get("q") || "";
const page = parseInt(searchParams.get("page")) || 1;
const limit = parseInt(searchParams.get("limit")) || 10;
const skip = (page - 1) * limit;
const articles = await prisma.post.findMany({
where: {
title: {
contains: q, // 关键:模糊匹配
mode: "insensitive", // 忽略大小写
},
},
skip,
take: limit,
orderBy: { createdAt: "desc" },
});
const total = await prisma.post.count({
where: {
title: {
contains: q,
mode: "insensitive",
},
},
});
return Response.json({
data: articles,
meta: {
search: q,
page,
limit,
total,
totalPages: Math.ceil(total / limit),
},
});
}
关键参数:
contains
= 模糊查找mode: 'insensitive'
= 大小写不敏感
这让你的搜索框具备了"生活中搜索栏那种聪明感"。
🤹♀️ 五、分页 + 模糊搜索的协奏曲
翻页时带着模糊条件,就像点菜时要服务员记住"少糖但加珍珠"。
请求示例👇
bash
GET /api/posts/search?q=next&page=2&limit=10
即:
搜索标题中包含 "next" 的内容,
并返回第2页的10条结果。
这在前端中可以这么写:
javascript
// /app/posts/page.jsx
'use client';
import { useState, useEffect } from 'react';
export default function PostList() {
const [data, setData] = useState([]);
const [page, setPage] = useState(1);
const [q, setQ] = useState("");
useEffect(() => {
fetch(`/api/posts/search?q=${q}&page=${page}&limit=10`)
.then(res => res.json())
.then(json => setData(json.data));
}, [page, q]);
return (
<div>
<input
placeholder="🔍 搜索文章..."
value={q}
onChange={(e) => setQ(e.target.value)}
/>
<ul>
{data.map(post => <li key={post.id}>{post.title}</li>)}
</ul>
<button onClick={() => setPage(p => p - 1)} disabled={page === 1}>上一页</button>
<button onClick={() => setPage(p => p + 1)}>下一页</button>
</div>
);
}
结果就是一个简洁的、反应灵敏的模糊分页搜索系统。
📊 六、性能与底层机制:别让数据库走神
分页与模糊搜索在底层数据结构上的区别:
技术层面 | 关键点 | 性能瓶颈 |
---|---|---|
LIMIT + OFFSET | 简单高效,分页首选 | OFFSET数大时查询慢 |
LIKE '%key%' | 通配符匹配,灵活 | 缺少索引,扫描量大 |
Full Text Search | 借助倒排索引,提高模糊性能 | 建索引成本高 |
Cursor-based Pagination | 使用游标代替页码 | 更适合集合更新频繁场景 |
建议:
- 内容量较小 →
OFFSET
模式足够 - 内容量巨大 → 使用 游标分页 或 全文索引方案
🧠 技术不是黑魔法,而是不断优化的折中哲学。
🎨 七、视觉层可视化:分页就像翻书页 📖
ini
+--------------------------------------------------+
| [1] [2] [3] [4] ... [99] ▶︎ |
| 🔍 搜索: [next-js 分页教程] |
+--------------------------------------------------+
| title: Next.js pagination tutorial |
| title: Fuzzy search in Next.js |
| title: Building scalable API routes |
| ... |
+--------------------------------------------------+
每次点击"下一页",客户端只是修改了 URL 参数:
ini
?page=3&q=fuzzy
而不是重新下载整个宇宙的数据。
这是 Web 的浪漫:
"按页面取数据,就像翻一本书,而不是重刷整个世界。"
⚡ 八、代码之外的思考:分页是一种文明
分页不仅是优化流量,更是一种数字伦理------
在信息过载的时代,我们不该一口吞下数据库。
模糊搜索,则是对人类记忆模糊性的技术礼让。
🪶 "我们不强行要求用户记得确切的词,而是让机器学会理解'大概'。"
这是计算机科学的温柔表达:
让算法去适应人,而不是让人适应算法。
🧭 九、总结清单
概念 | 核心要点 | 应用层 |
---|---|---|
分页 | LIMIT + OFFSET,控制数据流量 | API层性能优化 |
模糊搜索 | LIKE / contains + 字符串匹配 |
用户体验 |
组合使用 | 搜索结果分页展示 | 综合实战 |
优化思路 | 游标 + 索引 + 缓存 | 可扩展架构 |