---
title: ブログに簡易的な検索機能をつけました
description: ライブラリ無しのpure-js、クライアントサイドで動作します
thumbnail: 
emoji: 🔍
date: 2026-10-08
category: Tech
tags: []
index: true
published: true
---


このブログも1年以上経ち、記事数も着々と増えてきました。
それに伴い、あの記事を編集したいと思っても探し出すのが大変になることもしばしば。(特に途中で放置している記事を含めると)

そんなわけで検索機能をつけようと考えたのですが、このブログは運営コストを最小限にすることを重視しているため、SSGで構築されています。
SSGとは、簡単にいうと全てのページを事前にhtmlとして生成しておいて、後はそれを配布するだけの方法です。
これをCDNと組み合わせれば、もはやサーバーの負荷はほぼ0です。

しかし、SSGは事前に全てのページを把握している必要があり、ユーザーが入力するまで結果のわからない検索機能を実装することができません。
そこで、検索処理をクライアントサイドのjsで行い、簡易的なCSRとすることで実現してみました。

完成品:
https://moris.day/blog/search/

## 設計
基本的な仕組みは記事のタイトルやヘッダー、タグなどをまとめたjsonファイルをビルド時に生成(本文はサイズが大きくなりすぎるので除外)、
クライアントのjsで`String.includes()`で簡易的に検索します。


## 実装
検索用jsonの生成処理

↓ 実際に生成されたjsonファイル
https://moris.day/blog/api/search.json

```ts
function search_data() {
    let data: string[][] = []

    const posts = Posts();

    for (let postid of posts) {
        const post = Load(postid)
        const metadata = post.metadata

        if (!(dev||metadata.published)){continue}

        let headers: string[] = []
        let mdast = remark().parse(post.post)
        visitParents(mdast, 'heading', (node)=>{
            headers.push(toString(node))
        })

        let keyswords: string[] = [postid, metadata.title, metadata.description, metadata.category, ...metadata.tags, ...headers]
        
        data.push(keyswords.map((i)=>i.toLowerCase()))
    }

    return data
}
```

---

実際に検索してsvelteで結果を表示する部分
```html
<script lang="ts">
let search_data:any[][]
let result:any[][]|undefined = $state()

async function search() {
    if (!search_data) {
        const res = await fetch("../api/search.json")
        search_data = await res.json()
    }

    let search_elem = document.getElementById("search")

    if (search_elem instanceof HTMLInputElement && search_elem.value){
        let query = search_elem.value.toLowerCase()

        result = search_data.filter((post_data)=>{
            return post_data.some((item:any)=>item.includes(query))
        })
    } else {
        result = undefined
    }
}
</script>

{#if result?.length}
    <ul>
        {#each result as post}
        <li><a href="../post/{post[0]}">{post[1]}</a></li>
        {/each}
        </ul>
{:else if result?.length==0}
    <p>no result</p>
{/if}
```



## 検索速度計測
```js
const s = performance.now()

// 検索処理

const e = performance.now()
console.log(`search in: ${e-s}ms`)
```

初回はjsonファイルの読み込み等で100ms以上かかりますが、以降は1ms程度で終了しています。


## 進展
今のところ、検索対象はタイトルや見出しなどで、本文が検索できていない問題をどうにかしたいです。
かといって本文を全てダウンロードすると転送量が大きすぎるので、ビルド時にAIで重要そうな単語を抽出とかできたら面白そうです。

あとはスペース区切りでAND演算できるようにもしたいですね。