Jekyll 기반 깃허브 블로그의 검색기능을 Algolia 로 바꾸는 작업을 했습니다. plugin 을 이용해 손쉽게 변경할 수 있습니다.
1. Jekyll 블로그 검색 기능
기존 검색 작동방식
- 빌드시
_site/assets/js/data/search.json
생성- 모든 posts 에 대한 메타 데이터와 스니펫 작성
- json 파일 대상으로 문자열 매칭 수행 (공백, 특수기호 단위로 토큰 분리)
이게 꽤 성능이 좋다. 로컬 파일 검색이라 장애도 없다. 다만 형태소 분석이 아쉽다.
검색창 입력 및 출력
검색창 _includes/topbar.html
1
2
3
4
5
6
7
| <i id="search-trigger" class="fas fa-search fa-fw"></i>
<span id="search-wrapper" class="align-items-center">
<i class="fas fa-search fa-fw"></i>
<input class="form-control" id="search-input" type="search"
aria-label="search" autocomplete="off" placeholder="...">
</span>
<span id="search-cancel" ></span>
|
검색 설정 _includes/search-loader.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| <script src=""></script>
<script>
SimpleJekyllSearch({
searchInput: document.getElementById('search-input'),
resultsContainer: document.getElementById('search-results'),
json: '/assets/js/data/search.json',
searchResultTemplate: '',
noResultsText: '',
templateMiddleware: function(prop, value, template) {
if (prop === 'categories') {
if (value === '') {
return `${value}`;
} else {
return `<div class="mr-sm-4"><i class="far fa-folder fa-fw"></i>${value}</div>`;
}
}
if (prop === 'tags') {
if (value === '') {
return `${value}`;
} else {
return `<div><i class="fa fa-tag fa-fw"></i>${value}</div>`;
}
}
}
});
</script>
|
search.js 의 설정 정보 : _data/assets/cross_origin.yml
1
2
3
| # _data/assets/cross_origin.yml
search:
js: https://cdn.jsdelivr.net/npm/simple-jekyll-search@1.10.0/dest/simple-jekyll-search.min.js
|
생성된 /assets/js/data/search.json
일부
1
2
3
4
5
6
7
8
9
10
11
12
| [
{
"title": "jekyll 블로그에 Algolia 검색 기능 붙이기",
"url": "/posts/2023-10-05-jekyll-algolia-plugin/",
"categories": "frontend",
"tags": "git-blog, algolia, jekyll",
"date": "2023-10-05 00:00:00 +0900",
"snippet": " Jekyll 기반 깃허브 블로그의 검색기능을 Algolia 로 바꾸는 작업을 했습니다. plugin 을 이용해 손쉽게 변경할 수 있습니다.1. Jekyll ..."
}
// ...
// 작성된 md 파일들의 내용이 거의 그대로 실린다 (용량이 크다)
]
|
검색 결과 _includes/search-results.html
1
2
3
4
5
6
7
8
| <div id="search-result-wrapper" class="d-flex justify-content-center unloaded">
<div class="col-12 col-sm-11 post-content">
<div id="search-hints">
{_% include trending-tags.html %_}
</div>
<div id="search-results" class="d-flex flex-wrap justify-content-center text-muted mt-3"></div>
</div>
</div>
|
데이터소스를 클라우드로 전송하고, 인덱싱하여 클라우드의 검색엔진으로부터 검색 결과를 요청할 수 있는 검색 SaaS 입니다.
사용법
- 회원가입 및 로그인
- Dashboard 이동 및 Application 생성 (기본)
- (왼쪽 사이드바) Search 메뉴에서 Index 생성
- 데이터소스 업로드
- Index 하위 메뉴에서 UI Demos 로 쿼리 테스트
API Keys
- 왼쪽 사이드바 하단의
Settings
이동 - Team and Access 섹션의
API Keys
이동 - Your API Keys
- Application ID : 필수
- Search-Only API Key : algoliasearch 클라이언트 생성시 사용
- Admin API Key : Index 업로드에서 사용
인덱스
- 문서 인덱스와 사전 (토큰 뭉치) : 문서로부터 생성
- 쿼리 제안 (Suggestions) : 자주 사용되는 검색어를 기반으로 생성
- 쿼리 분류 : AI 모델을 사용한 프리미엄 서비스 (유료)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| const searchClient = algoliasearch(
'latency',
'6be0576ff61c053d5f9a3225e2a90f76'
);
const search = instantsearch({
indexName: 'instant_search',
searchClient,
});
search.addWidgets([
instantsearch.widgets.refinementList({
container: document.querySelector('#brand'),
attribute: 'brand',
})
]);
search.start();
|
1
2
3
4
5
6
7
8
9
| <div class="ais-InstantSearch">
<h1>InstantSearch.js e-commerce demo</h1>
<div class="right-panel">
<div id="searchbox"></div>
<div id="hits"></div>
<div id="pagination"></div>
</div>
</div>
|
설치
jekyll-algolia
설치 : Gemfile 수정 후 bundle install
실행_config.yml
에 algolia 설정 추가 (APP_ID, INDEX_NAME)jekyll-algolia
실행 : ADMIN_API_KEY 필요deploy.sh
작업에 indexing 단계 추가 (실패!!)- algolia 대시보드에서 인덱싱 조회 (쿼리 테스트)
1
2
3
4
| # Gemfile
group :jekyll_plugins do
gem 'jekyll-algolia', '~> 1.0'
end
|
1
2
3
4
| # _config.yml
algolia:
application_id: '${ALGOLIA_APP_ID}'
index_name: '${your-index-name}'
|
1
2
3
4
5
6
7
8
9
10
11
12
13
| # tools/deploy.sh
indexing() {
# upload to algolia
ALGOLIA_API_KEY="${ALGOLIA_API_KEY}" bundle exec jekyll algolia --config "$_config"
}
main() {
# ...
echo "indexing by algolia..."
indexing
# ...
}
|
github Action 에서 처리하도록 설정했는데, 실패했다. (Node v12 요구)
1
2
3
4
5
| indexing by algolia...
Error: Process completed with exit code 1.
==> continuous-delivery :
The following actions uses node12 which is deprecated and will be forced to run on node16
|
인덱싱
algolia 의 인덱스로 데이터를 전송한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| $ ALGOLIA_API_KEY="${ALGOLIA_API_KEY}" bundle exec jekyll algolia --config "$_config"
Processing site...
Jekyll Feed: Generating feed for posts
Rendering to HTML (100%) |===================================================|
Extracting records (100%) |==================================================|
Updating settings of index
Getting list of existing
Updating records in index
Records to delete: 0
Records to add: 3631
Updating index (100%) |======================================================|
✔ Indexing complete
|
검색 UI
vendor/bundle/ruby
아래의 jekyll-theme-chirpy-5.2.1/_includes/
에 있는 html 파일들을 최상단 동일 디렉토리로 복사한 다음에 작업한다.
검색창 _includes/topbar.html
1
| <div id="search-searchbar"></div>
|
검색 설정 _includes/search-loader.html
- CDN :
algoliasearch@4.20.0
- CDN :
instantsearch.js@4.57.0
- algoliasearch 클라이언트 생성
- instantsearch 질의 및 출력기 설정
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
| <script src="https://cdn.jsdelivr.net/npm/algoliasearch@4.20.0/dist/algoliasearch-lite.umd.js" integrity="sha256-DABVk+hYj0mdUzo+7ViJC6cwLahQIejFvC+my2M/wfM=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/instantsearch.js@4.57.0/dist/instantsearch.production.min.js" integrity="sha256-foJtB+Wd0wvvK+VU3KO0/H6CjwSwJfB1RnWlgx0Ov9U=" crossorigin="anonymous"></script>
<script>
console.log("going search-loader.html");
// algolia: APPLICATION_ID, SEARCH_API_KEY
const searchClient = algoliasearch('${AGOLIA_APP_ID}', '${AGOLIA_SEARCH_API_KEY}');
// algolia: index_name
const search = instantsearch({
indexName: '${AGOLIA_INDEX_NAME}',
searchClient,
});
const hitTemplate = function(hit) {
const title = hit._highlightResult.title.value;
const content = hit._highlightResult.html.value;
const html = content.replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
return `
<div class="post-item">
<span class="post-meta">${hit.date}</span>
<h2><a class="post-link" href="${hit.url}">${title}</a></h2>
<div class="post-snippet">${html}</div>
</div>
`; // 날짜, 제목, 본문 발췌
}
search.addWidgets([
instantsearch.widgets.searchBox({
container: '#search-searchbar', // 입력창
placeholder: 'Algolia Search...',
}),
instantsearch.widgets.hits({
container: '#search-hits', // 출력영역
templates: {
item: hitTemplate
}
})
]);
search.start();
</script>
|
검색 결과 _includes/search-results.html
1
| <div class="post-list" id="search-hits"></div>
|
9. Review
- 작동되는 것까지는 확인했는데, 다시 simple-jekyll-search 로 원상복구했다.
- index.html 의 posts 리스트를 함께 조정해야 해서 복잡하고
- 인덱싱 내용이 html 태그를 포함하고 있어서 후처리가 필요했다.
- 이쁘게 출력하게 만들기가 어렵고 번거로워 포기
- algolia 붙이는 작업을 하려면 search.html 을 따로 만드는게 좋겠다.
- 프론트엔드 실력이 좋으면 이거저거 해볼게 많은데, 참 안타깝다. 흐유~
- Algolia 키워드 제안(search suggestion) 기능을 시험해보지 못했다.
참고문서
nodejs 용 검색 라이브러리 : lunr.js
- jekyll 에서 생성하는 search.json 파일을 이용해서 검색 기능을 만들 수 있다.
simple-jekyll-search
의 대체제
참고문서
1
2
3
4
5
6
7
8
9
10
11
12
13
| var idx = lunr(function () {
this.field('title')
this.field('body')
this.add({
"title": "Twelfth-Night",
"body": "If music be the food of love, play on: Give me excess of it…",
"author": "William Shakespeare",
"id": "1"
})
})
idx.search("love")
|