어떤 데이터에 맞는 구조일까

공개된 매장 목록처럼 이용자가 같은 자료를 읽는 서비스는 정적 JSON 파일로 시작할 수 있습니다. 한 번 받은 목록을 브라우저 메모리에 두면 검색어를 바꿀 때마다 데이터베이스에 물을 필요가 없습니다. 사용자별 비공개 자료나 실시간 재고처럼 요청마다 달라지는 값에는 다른 구조가 필요합니다.

정적 파일이라는 말이 서버가 전혀 없다는 뜻은 아닙니다. HTML·스크립트·자료 파일을 전달할 호스팅은 필요하고, 자료를 고쳐 배포하는 운영 과정도 남습니다. 이 글은 구조 선택과 검색 예제를 설명하며 비용 절감이나 응답시간 개선 수치를 주장하지 않습니다.

로컬 바쿠오 소스에서 확인한 흐름

바쿠오의 로컬 demo/scripts/build-dataset.mjs는 처리한 자료를 public/data/exchanges.json으로 씁니다. demo/src/api/exchangeService.ts는 설정된 데이터 URL을 가져오고, HTTP 오류를 검사한 뒤 목록을 필터링합니다. 이 경로를 따라가면 데이터 생성 시점과 브라우저 사용 시점을 구분할 수 있습니다.

같은 로컬 프로젝트에는 환율을 별도 요청으로 가져오는 코드도 있습니다. 그러므로 매장 목록의 정적 제공을 서비스 전체의 “외부 API 없음”으로 설명하면 부정확합니다. 또한 demo와 frontend가 함께 있어 현재 배포본과 같은 구현인지는 별도 확인이 필요합니다.

먼저 크기와 갱신 조건 확인하기

다운로드 크기는 필드 수, 문자열 길이, 언어별 중복에 영향을 받습니다. 파일의 바이트 수와 실제 HTTP 전송량을 각각 확인하고, 휴대전화에서 첫 목록이 보일 때까지의 시간을 봅니다. 파일이 작아도 긴 자바스크립트 실행이나 마커 생성이 화면을 막을 수 있습니다.

자료 기준일과 사이트 배포일은 다를 수 있습니다. 원본 제공기관의 기준일을 기록하고, 갱신할 때는 누락·중복·필수값을 검사합니다. 개인정보나 내부 열을 정적 파일에 넣으면 다운로드한 사람이 볼 수 있으므로 공개 가능한 열만 포함합니다.

작은 목록으로 검색 실행하기

아래 전체 코드는 브라우저 콘솔이나 Node.js에서 실행할 수 있습니다. 실제 사업자가 아닌 두 개의 설명용 자료를 사용합니다. HTTP 요청은 하지 않으며, 목록 검증과 메모리 검색만 확인합니다.

function prepareDirectory(input) {
  if (!Array.isArray(input)) throw new TypeError('Directory must be an array');
  return input.map((record, index) => {
    if (!record || typeof record.id !== 'string'
        || typeof record.name !== 'string' || typeof record.address !== 'string'
        || !record.id.trim() || !record.name.trim() || !record.address.trim()) {
      throw new TypeError('Invalid directory record at index ' + index);
    }
    return {
      ...record,
      searchText: (record.name + ' ' + record.address).normalize('NFC').toLowerCase()
    };
  });
}

function searchDirectory(records, query) {
  if (typeof query !== 'string') throw new TypeError('Query must be a string');
  const needle = query.normalize('NFC').trim().toLowerCase();
  return records.filter(record => record.searchText.includes(needle));
}

// Synthetic directory entries for this example, not real businesses.
const records = prepareDirectory([
  { id: 'A', name: 'Harbor Exchange', address: 'Example Road' },
  { id: 'B', name: 'Garden Exchange', address: 'Sample Street' }
]);
console.log(searchDirectory(records, 'HARBOR').map(record => record.id)); // [ 'A' ]
console.log(searchDirectory(records, '').length); // 2
console.log(searchDirectory(records, 'missing').length); // 0

출력은 ["A"], 2, 0입니다. 빈 검색어는 전체 목록을 뜻하고, 일치하지 않는 검색어는 정상적인 빈 결과입니다. 잘못된 자료를 빈 배열로 바꿔 성공처럼 표시하지 않도록 형식 오류는 예외로 드러냅니다.

배포와 실패 상태까지 설계하기

실제 화면에서는 불러오는 중, 정상 목록, 검색 결과 없음, 파일 로딩 실패를 구분해야 합니다. 재시도 버튼은 실제 요청을 다시 수행해야 하며, 오류 때 임의 매장이나 오래된 자료를 최신 정보처럼 보여주지 않습니다. 이전 자료를 의도적으로 보관한다면 그 기준일과 사용 이유를 알려야 합니다.

배포 후에는 새 파일과 이전 HTML이 섞이지 않는지도 확인합니다. 데이터 파일 이름·캐시 정책·갱신 방법을 함께 설계하고 실제 HTTP 헤더로 확인합니다. 이 사이트의 캐시 설정을 확인하는 글에서 배포 파일의 역할별 정책을 이어서 살펴볼 수 있습니다.