
【Next.js 15】 サイト内検索(記事検索)機能をブログに実装する方法
公開日:
更新日:
デモ 完成イメージ
本サイトで使用しているサイト内検索機能(記事検索機能)を紹介します。↓
ポイント
・URLを読み取り、記事一覧ページでない場合は非表示になります。
・検索をかけた結果をsearch-result
ページで表示している形になります。
・ライブラリなどは特に使用していません。
・検索は至ってシンプルで 入力されたキーワードを記事のdescription(要約)が含んでいるかで判断しています。
ディレクトリ構成&開発環境
フォルダ構成は下記の形になります。↓
フォルダ構成図
1/
2└ src
3 ├ app
4 │ ├ components
5 │ │ ├ article.json
6 │ │ ├ SearchArticle.tsx
7 │ │ └ SearchResult.tsx
8 │ └ search-result
9 │ └ page.tsx
10 └ layout.tsx
11
・記事データはJson形式で取得します。(データベースは使用しません)
・SearchArticle.tsx
で検索キーワードが入力されたらsearch-result
ページに飛ばして結果を表示させます。
開発環境
・言語: Typescript
・ライブラリ: React
・フレームワーク: Next.js (バージョン:15.0.3 App Router形式)
・CSSフレームワーク: Tailwind CSS
コード実例
article.json
1
2{
3 "articleList": [
4 {
5 "genre":"記事のジャンル名",
6 "id":2,
7 "url": "記事のURL",
8 "date": "2025/05/14",
9 "description": "記事の説明文",
10 "image":"サムネイル画像のURL"
11 },
12 {
13 "genre":"記事のジャンル名",
14 "id":1,
15 "url": "記事のURL",
16 "date": "2025/05/13",
17 "description": "記事の説明文",
18 "image":"サムネイル画像のURL"
19 },
20 ]
21}
22
23
・ article.json
では記事の情報を持ちます。
・ description
内に書いてある文字列が検索キーワードと一致すれば検索ヒットとみなします。
SearchArticle.tsx
1/* eslint-disable */
2"use client";
3import { Link, TextField } from "@mui/material";
4import { usePathname } from "next/navigation";
5import SearchIcon from "@mui/icons-material/Search";
6import { useEffect, useState } from "react";
7import { useRouter } from "next/navigation";
8import { useSearchParams } from "next/navigation";
9
10const SearchArticle = () => {
11 const router = useRouter();
12 const url = "search-result?keyword=";
13 const clear = () => {
14 setInputText("");
15 };
16 const handleClick = () => {
17 clear();
18 };
19 const [inputText, setInputText] = useState("");
20 const [query, setQuery] = useState(url);
21 const path = usePathname();
22 const searchParams = useSearchParams();
23
24 const checkPath =
25 path == "/" ||
26 path.includes("search-result");
27
28 useEffect(() => {
29 clear();
30 }, [path]);
31
32 return (
33 <>
34 <div
35 className={
36 !checkPath
37 ? "hidden"
38 : "visible mt-3 w-full p-3 bg-white shadow-md rounded text-center "
39 }
40 >
41 <TextField
42 onKeyDown={(e) => {
43 if (e.keyCode === 13) {
44 // エンターキー押下時の処理
45 handleClick;
46 router.push(query);
47 }
48 }}
49 sx={{ width: "90%" }}
50 value={inputText}
51 onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
52 setInputText(event.target.value);
53 setQuery(url + event.target.value);
54 }}
55 margin="dense"
56 variant="outlined"
57 label="サイト内検索"
58 InputProps={{
59 endAdornment: (
60 <Link href={query}>
61 <button
62 onClick={handleClick}
63 className="my-2 -mr-1.5 bg-[#798777] border-gray-400 font-bold group relative inline-flex h-9 items-center justify-center overflow-hidden rounded-md border w-20 text-white transition-all duration-100 [box-shadow:5px_5px_rgb(82_82_82)] hover:translate-x-[3px] hover:translate-y-[3px] hover:[box-shadow:0px_0px_rgb(82_82_82)] gap-2"
64 >
65 <SearchIcon sx={{ fontSize: 20 }} />
66 検索
67 </button>
68 </Link>
69 ),
70 }}
71 />
72 </div>
73 </>
74 );
75};
76
77export default SearchArticle;
78
・ checkPath
変数で今どこのページなのかを判断します。記事一覧を表示するページだった場合はtrueとなりSearchArticle
コンポーネントがvisible
になります。
・ TextField
で何かキーワードが入った状態で検索ボタン(もしくはエンターキー)が押されるとsearch-result
ページに飛びます。 その際キーワードをクエリパラメータとして送ります 。
page.tsx
1import { Suspense } from "react";
2import SearchResult from "../components/SearchResult";
3
4export default function Page() {
5 return (
6 <>
7 <Suspense>
8 <SearchResult />
9 </Suspense>
10 </>
11 );
12}
13
・後述するSearchResult.tsx
ではuseSearchParams()
というメソッドを使用することになります。useSearchParams()を使用しているコンポーネントは<Suspense>でラップされなければなりません
SearchResult.tsx
1"use client";
2
3import Pagination from "../components/Pagination";
4import Breadcrumb from "../components/Breadcrumb";
5import { useSearchParams } from "next/navigation";
6import data from "../components/article.json";
7
8export default function SearchResult() {
9 const searchParams = useSearchParams();
10 const keyword = searchParams.get("keyword") ?? "";
11
12 let sortedData = data.articleList.filter((item: { description: string }) =>
13 item.description.includes(keyword)
14 );
15
16
17 const showSearchParam = () => {
18 return (
19 <>
20 <div className="flex flex-row bg-gray-200 gap-1 mt-3 ">
21 <div className="bg-[#798777] w-1 "></div>
22 <h2 className="text-lg font-semibold p-1 ">
23 「 {keyword} 」の検索結果は
24 {sortedData.length}件です
25 </h2>
26 </div>
27 </>
28 );
29 };
30
31 const showResult = (dataSize: number) => {
32 if (dataSize == 0) {
33 return (
34 <>
35 <div className="flex flex-row justify-between">
36 <p className="text-lg mt-4">
37 他のキーワードでお探し頂くか、もしくはカテゴリー選択をご利用下さい。
38 </p>
39 </div>
40 </>
41 );
42 } else if (dataSize >= 1) {
43 return (
44 <>
45 <div className="flex flex-row justify-between">
46 <Pagination articles={sortedData} />
47 </div>
48 </>
49 );
50 }
51 };
52
53 return (
54 <>
55 <div className="mb-3 w-full">{showSearchParam()}</div>
56 <div className="w-full">
57 <div>{showResult(sortedData.length)}</div>
58 </div>
59 </>
60 );
61}
62
・useSearchParams()
はurlからクエリパラメータ(今回の場合だと?keyword=以降の部分)を取得するのに使用します。
・Pagination
コンポーネントは こちらの記事で解説していますので詳細を知りたい方はご覧下さい。
・sortedData
が0件なら案内文言を表示します。1件以上ヒットした記事があれば記事一覧を表示します。
layout.tsx
1import Footer from "./Footer";
2import "./globals.css";
3import Header from "./Header";
4import SearchArticle from "./components/SearchArticle";
5import { Suspense } from "react";
6
7export default function RootLayout({
8 children,
9}: Readonly<{
10 children: React.ReactNode;
11}>) {
12
13 return (
14 <html lang="js">
15 <body>
16 <div className="flex flex-col min-h-screen ">
17 <Header />
18
19 <main className="flex-grow bg-slate-100 ">
20 <div className="md:flex h-full">
21 <section className="js-toc-content w-full md:w-2/3 flex flex-col items-center px-2 mt-4 pb-5">
22 {children}
23 </section>
24 <div className="md:w-1/3 flex flex-col items-center px-2 md:pl-1">
25 <Suspense>
26 <SearchArticle />
27 </Suspense>
28 </div>
29 </div>
30 </main>
31
32 <Footer />
33 </div>
34 </body>
35 </html>
36 );
37}
38
・SearchArticle
コンポーネントでもuseSearchParams()
メソッドを使用しているので<Suspense>でラップされなければなりません
・layout.tsx
にSearchArticle
を配置することで検索欄をサイトに表示しています。
まとめ
上記で紹介しているコンポーネントで検索機能を実現できます。

本記事は以上になります。
ご一読頂きありがとうございました。
目次