喂!我是 Wei

Front-End Engineer

Be a Problem Solver.

⌘K

導覽

所有文章緣起互動小功能

文章分類

目錄
設計思路PostMeta 的結構建立 <SearchBox /> 元件為什麼用 useMemo?在 Server Component 傳入資料搜尋範圍與限制與後端搜尋的比較小結

相關文章

用 Vercel AI SDK v6 在 Next.js 加上 AI 聊天助手(免費用 Groq)

2026年3月20日

前端工程師面試題:React Key 為什麼重要?useEffect dependency array 為什麼重要?

2026年6月7日

前端工程師面試題:MobX、Redux、Zustand 怎麼選?

2026年6月5日

最新文章
全部 →
前端 CI/CD 與正式環境除錯:從 Pull Request 到事故排查
2026-06-24
即時資料怎麼選?Polling、SSE、WebSocket 比較
2026-06-23
前端系統設計:如何拆元件、資料流與大型專案架構?
2026-06-22
無障礙不是加 ARIA:語意化 HTML、鍵盤操作與焦點管理
2026-06-21
CSS 與 RWD 面試整理:Flexbox、Grid、定位與層疊脈絡
2026-06-19
← 返回文章列表

Next.js Blog 不靠後端的全文搜尋

2026年3月8日·約 5 分鐘閱讀·
Next.js搜尋React

絕大多數的搜尋功能都需要後端:一個接收查詢字串的 API、一個資料庫或搜尋引擎。

但對個人技術 Blog 來說,文章數量通常不多,這時候有更簡單的做法:把所有文章的 metadata 直接傳給前端,在 Client 端過濾。不需要 API,不需要資料庫,搜尋結果即時出現。


設計思路

Server Component(頁面)
  │
  │  getAllPostMeta() → PostMeta[]
  │  ↓ 傳入 props
  └──▶ <SearchBox posts={posts} />
          │
          │  useState(query)
          │  useMemo → filter + slice
          └──▶ 即時顯示結果(不 re-fetch)

所有資料在 Server 端取得,傳給 Client Component 之後,後續的過濾完全在瀏覽器端完成。


PostMeta 的結構

搜尋針對的欄位是文章的 title、summary、tags,這些資料已經在 getAllPostMeta() 解析好了:

lib/posts.ts
export type PostMeta = {
  slug: string;
  title: string;
  date: string;
  summary: string;
  tags: string[];
  readingTime: number;
};

搜尋不需要文章的完整內文,只用 metadata 就夠了。這讓傳給 Client 的資料量保持很小。


建立 <SearchBox /> 元件

<SearchBox /> 是一個 Client Component,接收所有文章的 metadata,用 useMemo 計算搜尋結果:

components/SearchBox.tsx
"use client";
 
import { useMemo, useState } from "react";
import Link from "next/link";
import type { PostMeta } from "@/lib/posts";
 
export default function SearchBox({ posts }: { posts: PostMeta[] }) {
  const [q, setQ] = useState("");
 
  const results = useMemo(() => {
    const query = q.trim().toLowerCase();
    if (!query) return [];
 
    return posts
      .filter((p) => {
        // 把 title、summary、tags 合成一個字串搜尋
        const hay = [p.title, p.summary, p.tags.join(" ")]
          .join(" ")
          .toLowerCase();
        return hay.includes(query);
      })
      .slice(0, 8); // 最多顯示 8 筆
  }, [q, posts]);
 
  return (
    <div>
      <input
        value={q}
        onChange={(e) => setQ(e.target.value)}
        placeholder="搜尋文章…"
      />
 
      {q.trim() && (
        <div>
          {results.length ? (
            results.map((p) => (
              <Link key={p.slug} href={`/dashboard/posts/${p.slug}`}>
                <div>{p.title}</div>
                <div>{p.date}</div>
              </Link>
            ))
          ) : (
            <div>找不到結果</div>
          )}
        </div>
      )}
    </div>
  );
}

為什麼用 useMemo?

useMemo 讓過濾運算只在 q 或 posts 改變時重新執行,輸入每個字元不會每次都重新跑 filter。


在 Server Component 傳入資料

搜尋元件放在 sidebar 裡,由 Server Component 取得資料並傳入:

components/SidebarNav.tsx
import { getAllPostMeta } from "@/lib/posts";
import SearchBox from "@/components/SearchBox";
 
export default async function SidebarNav() {
  const posts = getAllPostMeta(); // ← Server 端讀取
 
  return (
    <aside>
      <SearchBox posts={posts} /> {/* ← 傳給 Client Component */}
      {/* ... 其他 sidebar 內容 */}
    </aside>
  );
}

getAllPostMeta() 用 React 的 cache() 包起來,在同一個 request 週期內只讀一次檔案,不會重複 I/O。


搜尋範圍與限制

這個實作的搜尋方式是 substring match,也就是關鍵字必須完整出現在文字中:

查詢結果
next✅ 符合含有「next」的標題
nxt❌ 不符合(不是 fuzzy search)
TOC 目錄✅ 符合(多字元空格分隔也 ok)

這種方式實作最簡單,對技術文章搜尋足夠用。如果之後文章量增加,可以換成 Fuse.js 實作 fuzzy search,API 介面幾乎一樣,只需要替換 filter 的邏輯。


與後端搜尋的比較

純前端搜尋(本文做法)後端 API 搜尋
延遲即時,無網路請求有網路往返延遲
複雜度低,不需要 API需要後端 + 資料庫
搜尋範圍僅 metadata可搜尋完整文章內文
適合規模幾十篇到上百篇文章量大時

對個人 Blog 來說,純前端搜尋是最務實的選擇。


小結

這個 Blog 的搜尋功能只用了 useState + useMemo,不需要任何後端:

  1. getAllPostMeta() 在 Server 端讀取所有文章 metadata
  2. 透過 props 傳給 <SearchBox />(Client Component)
  3. 使用者輸入關鍵字,useMemo 即時過濾結果

實作簡單,但對個人 Blog 來說完全夠用。


這是「如何打造這個 Blog」系列的最後一篇。五篇下來,從整體架構、MDX 文章系統、Code Highlight、TOC 目錄,到搜尋功能,把這個 Blog 的主要技術細節都走過一遍了。

分享:XLinkedIn
← 上一篇Next.js Blog 實作文章目錄(TOC)
下一篇 →Discord Bot 怎麼從零開始:申請、設定、第一個 Slash Command