서버 없이 사이트에 검색창 다는 법
페이지 목록만 있으면 읽는 일은 브라우저가 맡습니다. 대신 첫 방문자는 몇 초를 기다려야 합니다.
페이지 목록을 낼 수 있는 사이트라면 검색창에 서버가 필요 없습니다. 이 위젯은 방문자의 브라우저에서 사이트맵을 읽고, 거기 적힌 페이지를 하나씩 가져와 메모리에 색인을 만든 뒤 IndexedDB 에 하루 동안 보관합니다. 설치는 로더 한 줄과 빈 div 하나입니다. 어떤 기기에서 처음 온 방문자는 색인이 차는 몇 초를 기다리고, 그 기기의 다음 방문부터는 캐시가 바로 답합니다. Pagefind 가 빌드 단계에서 하는 일을 실행 시점으로 옮긴 것이라, 빌드가 없는 티스토리나 카페24, WordPress.com 에도 붙습니다.
정적 사이트가 마지막까지 갖지 못한 것
작은 사이트를 이루는 다른 기능들은 진작 정적으로 옮겨갔습니다. 페이지는 파일로 두고, 댓글은 위젯으로 달며, 뉴스레터 폼은 외부 서비스로 보냅니다. 검색만은 달랐습니다. 누군가 페이지를 전부 읽고 낱말 목록을 들고 있어야 했고, 그 역할은 늘 서버가 맡았습니다. WordPress 데이터베이스, 호스팅형 인덱스, 방문자를 외부로 보내는 Google 맞춤 검색이 그랬습니다. Pagefind는 페이지를 읽는 일을 빌드 단계로 옮기고, 낱말 목록을 정적 파일로 만들어 페이지 옆에 두는 방식으로 이 구조를 바꿨습니다. 다만 빌드가 있어야 쓸 수 있다는 한계는 남았습니다. 티스토리 블로그나 카페24 쇼핑몰에는 실행할 빌드가 없습니다.
이 위젯은 Pagefind의 발상은 그대로 두되, 읽는 시점을 방문자가 페이지를 여는 순간으로 옮깁니다. 페이지는 이미 공개돼 있고 호스트가 내보내고 있으므로, 방문자의 브라우저가 이미지를 받듯 페이지를 받아올 수 있습니다. 받아온 HTML은 파싱해 읽히는 본문만 남긴 뒤 낱말로 나눠 메모리 안의 역색인에 넣습니다. 이 색인은 브라우저 내장 데이터베이스인 IndexedDB에 사이트와 크롤 설정에 따라 이름을 붙여 저장합니다. 같은 기기로 다시 방문하면 페이지 대신 캐시를 읽습니다.
페이지 하나가 무엇으로 바뀌나
| 페이지에서 | 색인이 남기는 것 | 어디에 쓰나 |
|---|---|---|
<title>, 첫 <h1>, og:title | 제목 하나, h1 우선 | 결과의 굵은 첫 줄, 순위 계산에 가중 |
<meta name="description"> | 300자까지 | 순위 가중과 예비 발췌문 |
main, article, [role=main] 또는 data-bootsignal-body | 읽히는 본문 12,000자까지 | 가장 촘촘히 맞는 지점 앞뒤 30단위의 발췌문 |
id가 붙은 h2, h3, h4 | 소제목 글자와 위치 | 해당 섹션으로 바로 가는 하위 결과 |
nav, header, footer, aside, 스크립트, data-bootsignal-ignore | 없음 | 색인 전에 걷어내 메뉴 낱말이 모든 검색에 걸리지 않도록 함 |
<meta name="robots" content="noindex"> | 없음 | 해당 페이지를 건너뜀 |
data-pagefind-body와 data-pagefind-ignore도 그대로 읽습니다. Pagefind용으로 표시해 둔 사이트라면 같은 작업을 다시 하지 않아도 됩니다.낱말은 앞부분만 일치해도 검색됩니다. 위젯을 모두 입력하기 전에 위만 쳐도 후보가 뜨고, 영어에는 searching을 search로 줄이는 가벼운 어간 처리도 적용합니다. 한국어, 일본어, 중국어는 띄어쓰기로 나눌 수 없어 글자 두 개씩 짝지어 색인합니다. 사전이 없을 때 대부분의 CJK 검색 엔진이 쓰는 방식과 같습니다. 페이지가 <html lang>으로 언어를 표시하면, 한국어 페이지에서 검색하는 방문자는 색인에 한국어 페이지가 있는 한 해당 페이지만 봅니다.
설치
사이트맵이 가리키는 도메인으로 사이트 아이디 받기
로그인한 뒤 대시보드에 실제로 페이지가 표시되는 호스트 이름을 등록하고 사이트 아이디를 복사합니다. 로더는 그 아이디를 확인한 뒤에야 위젯을 그립니다. 크롤은 페이지와 같은 오리진에 있는 사이트맵 항목만 따라갑니다. 다만 사이트맵은
www.example.com이고 페이지는example.com에서 도는 경우에는 거부하지 않고 현재 오리진에 맞춰 처리합니다.로더와 빈 마운트 넣기
body를 닫기 전에 스크립트 태그 한 줄을 페이지마다 한 번 넣고, 검색창이 놓일 자리에는
div하나를 둡니다. 스레드 아이디는 없습니다. 검색은 페이지 단위가 아니라 사이트 단위로 작동하는 위젯입니다.<script src="https://bootsignal.com/bootsignal.js" data-site-id="YOUR_SITE_ID" data-lang="ko" defer></script> <div data-bootsignal="search"></div>검색창과 트리거 중에 고르기
기본값은 입력창과 결과를 그 자리에 표시합니다.
data-mode="modal"은 검색창 모양의 버튼을 그리고, 누르면 가운데 패널을 엽니다. Mac에서는 ⌘K, 다른 곳에서는 Ctrl K로 열 수 있으며, 아무것도 선택하지 않은 상태에서는/키로도 열립니다. 헤더에는 모달을, 검색 전용 페이지나 사이드바에는 인라인을 쓰면 됩니다.<div data-bootsignal="search" data-mode="modal" data-placeholder="문서 검색…"></div>크롤 범위 좁히기
사이트맵에는 태그 페이지, 페이지네이션, 글쓴이 보관함처럼 결과에 나오면 곤란한 주소가 섞여 있습니다.
data-exclude에는 건너뛸 경로 접두어를 쉼표로 적고,data-include는 적어 둔 접두어만 남깁니다.data-max-pages는 기본 300, 최대 1,000입니다. 페이지 목록을 JSON으로 직접 만들 수 있다면data-index로 그 파일을 가리키면 됩니다. 이때는 크롤하지 않습니다.<div data-bootsignal="search" data-include="/blog/,/docs/" data-exclude="/blog/tag/,/blog/page/" data-max-pages="400"></div>본문 위치 알려 주기
스킨이 본문을
main이나article없이div안에 넣는다면, 해당div에data-bootsignal-body를 붙이거나data-body로 선택자를 넘깁니다. 사이드바나 관련 글 묶음에는data-bootsignal-ignore를 붙입니다.id가 붙은 소제목은 하위 결과가 되는데, 대부분의 스킨에는 이미 붙어 있습니다.
방금 붙인 것
실제 위젯입니다. 이 사이트의 사이트맵을 읽어 같은 언어의 제품 지면을 지금 이 브라우저에서 색인합니다. 첫 색인에는 몇 초가 걸리지만, 이후 하루 동안은 캐시에서 바로 결과를 불러옵니다. 제품 이름에 들어간 낱말을 입력해 보세요.
이 지면의 위젯은 지금 이 브라우저에서 이 사이트의 제품 페이지를 색인하고 있습니다. 처음에는 진행 줄이 보이고, 같은 기기에서 새로고침하면 캐시가 답합니다.
로더 한 줄, 빈 div 하나, 그리고 이미 있는 사이트맵이면 됩니다.
설치하기 전에 알아 둘 한계
- 최대 1,000쪽이며, 상한을 따로 올리지 않으면 300쪽까지 읽습니다. 글이 5,000개인 사이트에서는 목록 앞쪽 페이지만 들어갑니다. 크롤 범위를 필요한 섹션으로 좁히거나
data-index파일을 두는 편이 알맞습니다. - 기기마다 첫 방문자가 크롤 비용을 부담합니다. 몇 초가 걸리고, 페이지당 요청 하나가 방문자의 회선으로 나갑니다. 캐시는 기본 24시간(
data-ttl, 시간 단위) 유지되며, 오래된 캐시는 바로 사용하면서 뒤에서 새로 만듭니다. - 오타는 허용하지 않습니다.
위젯을위잿으로 입력하면 아무것도 나오지 않지만,위까지만 입력하면 나옵니다. 앞부분 일치만 확인하고 편집 거리는 계산하지 않기 때문입니다. - 방문자가 열 수 있는 페이지만 색인합니다. 페이지를 가져올 때 그 방문자의 쿠키가 함께 실리고, 색인은 해당 브라우저 밖으로 나가지 않습니다. 따라서 회원 전용 글은 회원에게만 색인되고 다른 사람에게는 보이지 않습니다. 방문자에게 내주지 않을 페이지까지 찾아 줄 것이라고 기대하면 안 됩니다.
- 하위 결과는 소제목에
id가 있어야 나옵니다.<h2>에id를 붙이지 않는 스킨에서도 페이지 자체는 결과에 나오지만, 그 아래 앵커 링크는 빠집니다.
Pagefind, 호스팅형 인덱스, 그리고 이것
Pagefind는 오픈소스이며 빌드 단계에서 작동합니다. Hugo, Astro, Eleventy처럼 배포 파이프라인이 있는 사이트라면 그 방식이 알맞습니다. 색인을 미리 만들어 두므로 첫 검색부터 즉시 답하고, 구독할 필요도 없습니다. 이 위젯은 실행할 빌드가 없을 때 선택하는 방식입니다. WordPress.com, 티스토리, 카페24, 아임웹, Blogger, Squarespace가 여기에 해당하며, 검색창 하나 때문에 파이프라인을 관리하고 싶지 않은 경우도 마찬가지입니다.
Algolia의 DocSearch 같은 호스팅형 인덱스는 자체 서버에서 크롤하고 그곳에서 답합니다. 새 기기에서 처음 검색할 때도 빠르고 오타도 허용합니다. 대신 중간에 서비스 하나가 끼어듭니다. 신청이나 계정이 필요하고, 크롤러를 들여보내야 하며, 내 콘텐츠 사본이 다른 사람의 기계에 놓입니다. 이 위젯은 사본을 방문자 기기에 두고, 이미 내보내고 있는 페이지 외에는 아무것도 요구하지 않습니다.
자주 나오는 질문
- 새 글을 올리면 색인에 언제 들어가나요?
- 다음 크롤 때 들어갑니다. 방문자마다 캐시가 기본 24시간이므로 다시 온 방문자에게는 하루 안에, 처음 온 방문자에게는 바로 보입니다. 하루에 여러 번 글을 올리는 사이트는
data-ttl을 낮추거나, 위젯이 돌려주는 핸들의reindex()를 발행한 뒤에 부르면 됩니다. - 사이트가 얼마나 커지면 이 방식이 안 맞나요?
- 상한은 1,000쪽이고 편하게 쓸 수 있는 범위는 몇백 쪽입니다. 그보다 크면 첫 방문 크롤이 휴대전화에서 느껴질 만큼 길어집니다. 이때는 직접 만든
data-index파일이나 Pagefind 같은 빌드 타임 도구가 더 알맞습니다. - 첫 방문에서 검색창이 느린가요?
- 검색창은 바로 뜨고, 결과는 페이지가 들어오는 대로 채워집니다. 크롤 중에 입력한 검색어는 몇 페이지마다 다시 검색하며, 입력창 아래에는 색인이 계속 늘고 있음을 알리는 줄이 붙습니다. 40쪽짜리 사이트는 두어 초면 끝나고, 그다음 방문부터는 캐시가 답합니다.
- 메뉴 항목과 푸터 링크는 왜 검색에 안 잡히나요?
- 색인 전에 걷어내기 때문입니다. 내비게이션, 헤더, 푸터, aside, 그리고
data-bootsignal-ignore나data-pagefind-ignore가 붙은 요소는 모두 빠집니다. 그렇지 않으면 메뉴에 있는 낱말이 사이트의 모든 페이지에 같은 점수로 걸립니다. - 구독은 어디에서 확인되나요?
- 로더가 위젯을 그리기 전에 사이트 아이디를 활성 BootSignal 구독과 대조합니다. 모든 유료 위젯이 쓰는 같은 관문입니다. 등록 도메인 하나에 월 $20 또는 연 $200이며, 유료 위젯 38개가 모두 포함되고 트래픽에 따라 늘어나는 항목이 없습니다. 검색창이 만 번 쓰인 사이트와 열 번 쓰인 사이트가 같은 금액을 냅니다.
위 데모가 실제 위젯입니다. 내 사이트에 붙인 것은 내 사이트맵을 읽습니다.