GitHub 블로그에 Lunr 기반 커스텀 검색창 만들기
GitHub Pages 블로그에 사용자 정의 검색창을 추가하고 싶으셨나요?
이 포스트에서는 minimal-mistakes 테마를 기반으로 Lunr.js를 활용해 상단과 사이드바에 검색창을 구현하고, 제목 및 태그 기준의 검색 기능을 설정하는 방법을 단계별로 안내합니다.
해당 강의는 minimal-mistakes-jekyll 테마를 기준으로 작성되어있습니다.
1. 상단 위에 검색 창 만들기
_config.yml 수정
defaults:
- scope:
path: ""
type: pages
values:
search: true
author_profile: true
sidebar:
nav: "main"
search: true 코드를 추가해줍니다.
_config.yml 속성 수정
search: 해당 속성에 추가해줍니다: search: true
search_full_content: 해당 속성에 추가해 줍니다: search_full_content: true
search_provider: 해당 속성에 추가해 줍니다: search_provider: lunr
lunr: 해당 속성 하위 속성인 search_within_pages: 해당 속성에 추가해 줍니다: search_within_pages: true

속성 수정 후 local server로 확인한 스크린샷 이미지입니다.
오른쪽 상단에 Category 옆에 search 아이콘이 생성된 것을 확인할 수 있습니다.
해당 search 아이콘의 코드 위치는 \_includes\masthead.html 에 25번째 줄에 있습니다.
{% if site.search == true %}
<button class="search__toggle" type="button">
<span class="visually-hidden"
>{{ site.data.ui-text[site.locale].search_label | default: "Toggle search"
}}</span
>
<i class="fas fa-search"></i>
</button>
{% endif %}
해당 코드를 지우면 오른쪽 상단에 search 아이콘이 지워지는 것을 확인할 수 있습니다.
2. 사이드 바 카테고리 밑에 검색 창 추가하기
해당 코드를 \_include\sidebar.html 에 넣어 줍니다.
<div class="sidebar__search">
<form id="sidebar-search" onsubmit="return SidebarSearchHandler();">
<div class="search-block">
<select id="search-type">
<option value="title">Title</option>
<option value="tag">Tag</option>
<option value="both">Title + Tag</option>
</select>
</div>
<div class="search-block">
<input type="text" id="search-query" placeholder="Search input" />
</div>
<div class="search-block">
<button type="submit"><i class="fa fa-search"></i> Search</button>
</div>
</form>
</div>
해당 코드를 넣을 때, <nav class="nav__list"> … </nav> 해당 nav 태그 안에 넣어 주도록 합시다.
<form id="sidebar__search" onsubmit="return SidebarSearchHandler();"> 해당 부분에 SidebarSearchHandler() 함수는 2-2. 에서 확인 하실 수 있습니다.
2-1. 사이드 바 UI 수정 (.scss)
\_sass\minimal-misktakes\_sidebar.scss 에 추가
.sidebar__search {
font-family: $sans-serif;
font-size: $type-size-6;
margin-top: 1em;
vertical-align: top;
@include breakpoint($x-large) {
width: initial;
margin-inline-end: initial;
}
.search-block {
margin-bottom: 0.6em;
select,
input,
button {
width: 100%;
max-width: 100%;
display: block;
height: 2.2em;
font-size: 0.9em;
font-family: $inherit;
box-sizing: border-box;
border-radius: $border-radius;
border: 1px solid $border-color;
}
select {
appearance: none;
padding: 0 0.6em;
background-color: $background-color;
color: inherit;
}
input {
padding: 0 0.6em;
background-color: $background-color;
color: inherit;
}
button {
background-color: $primary-color;
color: $base07;
border: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 0.4em;
i {
font-size: 0.9em;
}
&:hover {
background-color: mix($primary-color, black, 80%);
}
}
}
}
해당 스타일까지 구현했다면, 로컬 서버로 테스트 할 때, UI가 보입니다. 작동은 아직은 안하지요.
2-2. SidebarSearchHandler() 함수 구현하기
\assets\js\ 해당 폴더에 sidebar-search.js 파일을 새로 만들기 해줍니다.
\assets\js\sidebar-search.js 코드
function SidebarSearchHandler() {
const query = document.getElementById("search-query").value;
const type = document.getElementById("search-type").value;
if (!query.trim()) return false;
let finalQuery = "";
if (type === "title") finalQuery = `title:${query}`;
else if (type === "tag") finalQuery = `tags:${query}`;
else finalQuery = query;
window.location.href = `/_search/?q=${encodeURIComponent(finalQuery)}`;
return false;
}
2-3. search page 추가하기
\_pages\ 경로에 search.md 새로 파일을 만들기 해줍니다.
search.md 코드
---
title: "search result"
layout: search
permalink: /_search/
---

serach.md를 추가하고 로컬 서버에서 http://localhost:4000/_search/ 해당 홈페이지가 만들어집니다.
2-4. serach.js 구현하기
assets\js\lunr\ 경로에 search.js 파일을 새로 만들기 합니다.
assets\js\lunr\search.js 코드
(function () {
const params = new URLSearchParams(window.location.search);
const query = params.get("q");
const searchInput = document.getElementById("search");
const resultsContainer = document.getElementById("results");
if (!searchInput || !resultsContainer) return;
if (typeof window.store === "undefined") return;
function renderResults(results, query) {
resultsContainer.innerHTML = "";
if (results.length === 0) {
resultsContainer.innerHTML = `<p><em>"${query}"</em>에 대한 결과가 없습니다.</p>`;
return;
}
const html = results
.map(
(post) => `
<div class="list__item">
<article class="archive__item" itemscope itemtype="https://schema.org/CreativeWork">
<h2 class="archive__item-title" itemprop="headline">
<a href="${post.url}" rel="bookmark">${post.title}</a>
</h2>
<div class="archive__item-excerpt" itemprop="description">
${post.excerpt?.slice(0, 150) ?? ""}...
</div>
</article>
</div>
`
)
.join("");
resultsContainer.innerHTML = html;
}
function performSearch(query) {
const results = [];
const lower = query.toLowerCase();
for (const post of window.store) {
const title = post.title?.toLowerCase() ?? "";
const excerpt = post.excerpt?.toLowerCase() ?? "";
const tags = post.tags?.join(" ").toLowerCase() ?? "";
const categories = post.categories?.join(" ").toLowerCase() ?? "";
let match = false;
if (lower.startsWith("title:")) {
const keyword = lower.replace("title:", "").trim();
match = title.includes(keyword);
} else if (lower.startsWith("tags:")) {
const keyword = lower.replace("tags:", "").trim();
match = tags.includes(keyword);
} else {
match =
title.includes(lower) ||
excerpt.includes(lower) ||
tags.includes(lower) ||
categories.includes(lower);
}
if (match) {
results.push(post);
}
}
renderResults(results, query);
}
if (query) {
searchInput.value = query;
performSearch(query);
}
searchInput.addEventListener("input", function (e) {
performSearch(e.target.value);
});
})();
동작 요약
- 검색창에 입력된 값을 실시간으로 감지
- 검색어(
q)가 URL에 있을 경우 자동으로 검색 수행 - 정적 데이터(
window.store)를 기준으로title,excerpt,tags,categories에서 키워드 검색 - 검색 결과를 실시간으로 DOM에 렌더링
2-5. serach.js
\_layouts\default.html 코드 추가:
<!-- lunr search script -->
<script src="/assets/js/sidebar-search.js"></script>
<script src="/assets/js/lunr/lunr.min.js"></script>
<script src="/assets/js/lunr/lunr-store.js"></script>
<script src="/assets/js/lunr/search.js"></script>
\_layouts\default.html 전체 코드:
---
---
<!doctype html>
{% include copyright.html %}
<html lang="{{ site.locale | replace: "_", "-" | default: "en" }}" class="no-js">
<head>
{% include head.html %}
{% include head/custom.html %}
</head>
<body class="layout--{{ page.layout | default: layout.layout }}{% if page.classes or layout.classes %}{{ page.classes | default: layout.classes | join: ' ' | prepend: ' ' }}{% endif %}" dir="{% if site.rtl %}rtl{% else %}ltr{% endif %}">
{% include_cached skip-links.html %}
{% include_cached masthead.html %}
<div class="initial-content">
{{ content }}
{% include after-content.html %}
</div>
{% if site.search == true %}
<div class="search-content">
{% include_cached search/search_form.html %}
</div>
{% endif %}
<div id="footer" class="page__footer">
<footer>
{% include footer/custom.html %}
{% include_cached footer.html %}
</footer>
</div>
<!-- JEKYLL LIQUID TAG -->
{% include custom_code-block_custom.html %}
{%include custom_toggle-script.html %}
{% include custom_quiz-form.html %}
<!-- lunr search script -->
<script src="{{ '/assets/js/sidebar-search.js' | relative_url }}"></script>
<script src="{{ '/assets/js/lunr/lunr.min.js' | relative_url }}"></script>
<script src="{{ '/assets/js/lunr/lunr-store.js' | relative_url }}"></script>
<script src="{{ '/assets/js/lunr/search.js' | relative_url }}"></script>
{% include scripts.html %}
</body>
</html>
저는 해당 부분을 custom_script.html 으로 분리 해서 사용했습니다.
custom_script.html 코드:
<!-- JEKYLL LIQUID TAG -->
{% include custom_code-block_custom.html %} {%include custom_toggle-script.html
%} {% include custom_quiz-form.html %}
<!-- lunr search script -->
<script src="{{ '/assets/js/sidebar-search.js' | relative_url }}"></script>
<script src="{{ '/assets/js/lunr/lunr.min.js' | relative_url }}"></script>
<script src="{{ '/assets/js/lunr/lunr-store.js' | relative_url }}"></script>
<script src="{{ '/assets/js/lunr/search.js' | relative_url }}"></script>
\_layouts\default.html 에 추가한 코드:
{% include custom_script.html %}

title 을 assembly 으로 검색한 이미지 하지만 처음에 Assembly Category까지 검색되어 나온 모습을 볼 수 있습니다.
\_config.yml 에서 defaults: 속성에서 수정:
- scope:
path: ""
type: pages
values:
author_profile: true
search: false
sidebar:
nav: "main"
카테고리까지 검색 되는 것을 방지 하기 위해 serach: 속성의 값을 false 으로 설정해주었습니다.

다시 검색을 진행 해본 이미지 입니다. 카테고리가 빠져있고 게시글만 검색되는 것을 확인할 수 있었습니다.
Leave a comment