ADR-0017: 검색을 한 자리에 두고 절 제목과 인라인 코드로 색인한다
상태
Accepted
날짜
2026-08-13
맥락
사이트가 성격이 다른 두 종류의 글을 담고 있습니다. 블로그 글 4 편과 아키텍처 문서 39 개이며, 문서 쪽에 절이 534 개 있습니다. 목록 화면에 4 편이 전부 보이는 블로그와 사이드바 4 단계를 접어 가며 찾아야 하는 문서는 탐색 부담이 다릅니다.
두 가지를 정해야 했습니다.
진입점을 몇 개 둘 것인가. 상단 바는 AppShell 이 소유하고 사이드바 유무만 자식 프레임이 정합니다. 영역마다 검색바를 두려면 검색의 소유를 프레임으로 내려야 하며, 그러면 진입점과 색인이 각각 두 벌이 됩니다.
무엇까지 색인할 것인가. 독자가 문서에서 찾는 것은 두 종류입니다. 스크롤 처럼 주제로 찾는 경우와 allowedHosts 처럼 식별자를 그대로 치는 경우입니다. 후자는 실측으로 확인한 함정을 다시 꺼내 볼 때의 접근 경로이며 절 제목에는 나타나지 않습니다.
결정
진입점은 하나이고 결과를 영역별로 나눕니다
상단 바에 검색 하나를 두고 오버레이 하나가 글과 문서를 함께 찾습니다. 결과는 글 · 아키텍처 · 소개 로 묶어 어느 쪽에서 나온 것인지 구분하며, 묶음의 차례는 상단 메뉴와 같습니다.
셸이 여는 상태를 소유하고 오버레이는 그것을 입력으로 받습니다. 트리거가 상단 바와 하단 네비 두 곳에 있으므로 오버레이가 상태를 소유하면 트리거들이 오버레이를 참조하게 되고 지연 로드가 무효가 됩니다. 같은 이유로 ⌘K · Ctrl+K 를 듣는 것도 셸입니다. 오버레이는 열릴 때 로드되므로 자신을 여는 열쇠를 가질 수 없습니다.
단축키는 KeyboardEvent.key 가 아니라 code 로 판정합니다. 한글 자판에서는 같은 자리가 ㅏ 로 오므로 key 만 보면 입력기를 켠 상태에서 단축키가 듣지 않습니다.
색인은 절 제목과 인라인 코드까지입니다
빌드가 search-index.ts 를 생성합니다. 문서마다 제목 · 설명 · 태그와 절 목록을 담고, 절마다 그 안에 나오는 인라인 코드를 함께 담습니다. 코드 블록은 제외합니다. 예제 전체가 들어오면 흔한 낱말이 모든 문서에 걸립니다.
인라인 코드를 절에 붙이므로 식별자로 찾은 결과도 앵커를 갖습니다. allowedHosts 로 찾으면 개발 환경 문서의 7 절로 바로 이동합니다.
| 자리 | 가중치 | 사유 |
|---|---|---|
| 문서 제목 | 6 | 문서 자체를 지목한 질의입니다 |
| 인라인 코드 | 5 | 식별자를 그대로 친 질의이며 같은 글자가 산문에 우연히 섞이지 않습니다 |
| 태그 | 4 | 글의 분류이며 작성자가 직접 부여합니다 |
| 절 제목 | 3 | 문서 안의 위치를 가리킵니다 |
| 설명 | 2 | 요약이라 낱말이 넓게 걸립니다 |
가중치는 자리마다 더합니다. 가장 높은 자리 하나만 취하면 httpResource 처럼 여러 문서가 쓰는 이름에서 그 이름을 절 제목으로 내건 문서와 본문에 한 번 언급한 문서가 같은 점수가 됩니다.
모든 낱말이 한 문서 안에서 발견되어야 결과에 듭니다. 형태소 분석 없이 부분 문자열로 맞추므로 라우팅 이 라우팅과 에 걸리며, 대가로 팅과 같은 조각도 걸립니다.
결과는 절 단위가 아니라 문서 단위로 내고 상한은 20 건입니다. 절마다 결과를 만들면 흔한 낱말 하나에 수백 건이 나오고, 상한을 넘기면 목록을 훑는 대신 스크롤이 판단을 대신하게 됩니다.
색인과 팔레트는 검색을 열 때 받습니다
색인은 압축 후 20kB 이고 팔레트 청크는 16.5kB 입니다. 검색을 쓰지 않는 방문에서는 한 번도 필요하지 않으므로 초기 번들에 두지 않습니다.
shared/markdown 배럴은 불러오는 함수만 내보내고 색인 자체는 내보내지 않습니다. 배럴에 실으면 사이드바가 문서 목록을 읽는 순간 색인이 함께 딸려 옵니다. 배럴이 @defer 분리를 막는 사례는 개발 환경 7절에 기록되어 있습니다.
검토한 대안
블로그와 문서에 검색바를 각각 배치
| 구분 | 내용 |
|---|---|
| 장점 | 문서를 보는 중에 글 결과가 섞이지 않습니다. 영역마다 결과 형식을 다르게 만들 수 있습니다 |
| 단점 | 검색의 소유가 셸에서 프레임으로 내려가 진입점과 색인이 두 벌이 됩니다 |
| 기각 사유 | 글이 4 편이라 블로그 검색바가 뒤지는 대상이 목록 화면에 이미 전부 보입니다. 두 벌을 유지할 대상 규모가 아닙니다 |
현재 영역을 기본 범위로 지정
| 구분 | 내용 |
|---|---|
| 장점 | 진입점은 하나로 두면서 문서를 보는 중에는 문서만 나옵니다 |
| 단점 | 검색 상태가 라우트에 의존합니다. 사용자가 지금 어느 범위를 뒤지고 있는지 매번 확인해야 합니다 |
| 기각 사유 | 결과를 영역별로 묶으면 섞임 문제가 해소되며, 범위를 상태로 두는 대가가 남지 않습니다 |
본문 전문 색인
| 구분 | 내용 |
|---|---|
| 장점 | 산문 안의 표현까지 찾을 수 있고 결과에 문맥 조각을 보여줄 수 있습니다 |
| 단점 | 압축 후 141kB 로 채택안의 7 배입니다 |
| 기각 사유 | 찾지 못해서 문제가 되는 것은 식별자였고 그것은 인라인 코드 801 개로 덮입니다. 나머지 121kB 가 사는 것은 산문 표현 검색이며, 문서가 절 번호 체계를 갖고 있어 그 수요가 구조적으로 낮습니다 |
모바일 진입점을 띄우는 버튼(FAB)으로 배치
| 구분 | 내용 |
|---|---|
| 장점 | 검색이 가장 눈에 띄고 엄지에 가깝습니다 |
| 단점 | 스크롤 중 본문 우하단을 계속 가립니다. 하단 네비 위에 띄우려면 막대 높이를 두 곳에서 맞춰야 합니다 |
| 기각 사유 | 하단 네비 항목이 셋이라 네 번째 칸이 비어 있었습니다. 빈 칸을 두고 그 위에 요소를 겹치는 구성이 됩니다 |
결과
문서 43 개와 글 4 편을 제목 · 절 제목 · 태그 · 인라인 코드로 찾을 수 있고, 절이나 식별자로 걸린 결과는 해당 절로 바로 이동합니다.
감수하는 사항은 다음과 같습니다.
- 산문 안의 표현은 찾지 못합니다. 절 제목에도 인라인 코드에도 없는 낱말은 결과에 나오지 않습니다.
- 부분 문자열로 맞추므로 조각이 걸립니다.
팅과같은 입력이라우팅과 네비게이션에 걸립니다. 형태소 분석기를 들이지 않는 대가입니다. - 초기 번들이 0.6kB 늘었습니다. 순위 계산 함수와 색인을 불러오는 함수가 배럴을 통해 들어옵니다. 색인 자체와 팔레트는 별도 청크입니다.
- 색인이 절 순서에 의존합니다. 인라인 코드를 절에 붙일 때 빌드가 목차와 같은 차례로 2·3 단계 제목을 읽어 짝지으므로, 목차 생성 규칙이 바뀌면 두 곳을 함께 고쳐야 합니다.
- 결과에 문맥 조각이 없습니다. 어느 절에서 걸렸는지는 보이지만 그 절의 어느 문장인지는 열어야 알 수 있습니다.
개정
2026-08-13 — 소개를 색인에서 제외했습니다. 상단 메뉴와 하단 네비에 항상 나와 있어 찾을 필요가 없고, 자기소개 문장의 흔한 낱말이 문서 결과 사이에 섞여 목록을 흐렸습니다. 결정 절의 결과 묶음은 글 과 아키텍처 둘로 줄었습니다.
2026-08-13 — 입력과 결과를 분리했습니다. 입력창은 즉시 반영하고 결과는 150ms 동안 입력이 멎은 뒤에 계산합니다. 한국어 입력기가 낱자마다 값을 내보내므로 라우팅 한 낱말에 여섯 번 넘게 목록 전체가 다시 그려지고 높이도 함께 움직였습니다.
2026-08-13 — 질의와 겹치는 구간을 강조합니다. 어느 낱말 때문에 걸린 결과인지 목록에서 바로 보이게 합니다. HTML 문자열이 아니라 조각 배열로 돌려주고 화면이 텍스트 보간으로 그립니다. 문자열을 만들어 [innerHTML] 로 넣으면 ADR-0013 이 빌드 생성물에만 열어 둔 경로가 런타임 입력에도 열립니다. 색은 --mark 토큰 짝을 새로 추가했습니다.
2026-08-13 — 좁은 화면에서 화면 전체를 씁니다. DOM 이 같고 크기와 모서리만 달라지므로 반응형이며 런타임 판정을 쓰지 않습니다. 높이 전환은 상자가 화면 안에서 커졌다 작아질 때의 문제이므로 이 폭에서는 스타일 규칙이 되돌립니다.