Vanilla JS로 클라이언트 사이드 라우팅과 검색 기능 구현하기

Frontend/JavaScript

이 글에서는 SPA에서 페이지 새로고침 없이 URL을 변경하고 검색 기능을 구현하는 방법을 알아본다.

History API를 활용한 클라이언트 사이드 라우팅은 프레임워크 없이도 효과적인 SPA를 구현할 수 있는 핵심 기술이다.

클라이언트 사이드 라우팅 구현

History API 활용

history.pushState()를 사용하면 페이지 새로고침 없이 브라우저의 URL을 변경할 수 있다.

// App.js

handleClick: async () => {
  // 브라우저 히스토리에 새 항목 추가 (URL을 홈으로 변경)
  history.pushState(null, null, "/");

  // 포켓몬 목록 데이터 다시 불러오기
  const pokemonList = await getPokemonList();

  // 앱 전체 상태 업데이트
  this.setState({
    ...this.state,
    pokemonList,
    type: "",  // 타입 필터 초기화
    searchWord: getSearchWord(),  // 검색어 초기화
    currentPage: "/",  // 현재 페이지 경로 업데이트
  });
}

history.pushState()의 첫 번째 인자는 상태 객체, 두 번째는 제목(대부분의 브라우저에서 무시됨), 세 번째는 변경할 URL이다.

이 메서드를 호출하면 브라우저의 주소창이 변경되지만 페이지는 새로고침되지 않는다.

 

URL 파라미터를 활용한 검색 기능

검색어를 URL에 반영하기

// App.js

handleSearch: async (searchWord) => {
  // 검색어를 URL에 반영 (/?search=검색어 형태)
  history.pushState(null, null, `?search=${searchWord}`);

  // 검색어와 현재 타입으로 필터링된 포켓몬 목록 불러오기
  const searchPokemonList = await getPokemonList(
    this.state.type,
    searchWord
  );

  // 검색 결과로 앱 상태 업데이트
  this.setState({
    ...this.state,
    searchWord,
    pokemonList: searchPokemonList,
    currentPage: `?search=${searchWord}`,
  });
}

검색어를 URL 쿼리 파라미터로 관리하면 (/?search=피카츄) 사용자가 URL을 공유하거나 북마크할 때 검색 상태가 유지된다.

한글 검색어는 자동으로 인코딩되며, decodeURIComponent를 사용하여 디코딩할 수 있다.

 

Header 컴포넌트 구현

조건부 렌더링

this.template = () => {
  const { currentPage, searchWord } = this.state;

  let temp = `<div class='header-content' id="title">
    <img src='/src/img/ball.webp' width=40px height=40px></img>
    포켓몬 도감</div>`;

  // 상세 페이지가 아닐 때만 검색 바 표시
  if (!currentPage.includes("/detail")) {
    temp += `<div class="search">
        <input type="text" placeholder="포켓몬을 검색하세요!" id="search" autocomplete="off" value="${decodeURIComponent(searchWord)}" />
        <button id="search-button"><img src="src/img/search.png"></img></button>
    </div>`;
  }

  return temp;
};

현재 페이지 경로를 확인하여 상세 페이지일 때는 검색 바를 숨긴다.

decodeURIComponent로 URL에 인코딩된 한글 검색어를 디코딩하여 input 필드에 표시한다.

 

이벤트 처리

this.render = () => {
  this.$target.innerHTML = this.template();

  // 타이틀 클릭 이벤트 - 홈으로 이동
  const $title = document.getElementById("title");
  $title.addEventListener("click", () => {
    this.handleClick();
  });

  // 검색 기능 (상세 페이지가 아닐 때만)
  if (!this.state.currentPage.includes("/detail")) {
    const $searchInput = document.getElementById("search");
    const $searchButton = document.getElementById("search-button");

    // 검색 실행 함수
    const performSearch = () => {
      this.handleSearch($searchInput.value);
    };

    // 엔터 키 입력 시 검색 실행
    $searchInput.addEventListener("keydown", (e) => {
      if (e.key === "Enter") performSearch();
    });

    // 검색 버튼 클릭 시 검색 실행
    $searchButton.addEventListener("click", performSearch);
  }
};

타이틀 클릭, 엔터 키 입력, 검색 버튼 클릭 등 다양한 방식으로 검색을 실행할 수 있도록 이벤트 리스너를 등록한다.

performSearch 함수를 별도로 정의하여 중복 코드를 줄였다.

 

 

popstate 이벤트 처리

브라우저의 뒤로가기/앞으로가기 버튼을 눌렀을 때 상태를 업데이트하려면 popstate 이벤트를 처리해야 한다.

window.addEventListener('popstate', () => {
  // URL 변경에 따라 상태 업데이트
  const currentPage = window.location.pathname + window.location.search;
  const searchWord = new URLSearchParams(window.location.search).get('search') || '';

  // 상태 업데이트 및 재렌더링
  this.setState({
    ...this.state,
    currentPage,
    searchWord
  });
});

사용자가 브라우저의 뒤로가기/앞으로가기 버튼을 사용해도 애플리케이션이 정상적으로 동작한다.

 

완성된 프로젝트

이러한 방식으로 구현하면 사용자가 페이지 전환 없이 포켓몬을 검색하고 결과를 확인할 수 있다. 같은 패턴을 적용하여 PokemonDetail 컴포넌트까지 구현하면 완전한 포켓몬 도감 SPA가 완성된다.

 

클라이언트 사이드 라우팅의 장점

  • 빠른 페이지 전환: 페이지 새로고침 없이 화면을 변경할 수 있다
  • 부드러운 사용자 경험: 네이티브 앱과 유사한 UX를 제공한다
  • 상태 유지: 검색어나 필터 상태를 URL에 저장하여 공유 가능하다
  • 브라우저 히스토리 활용: 뒤로가기/앞으로가기 버튼이 정상 작동한다

'Frontend > JavaScript' 카테고리의 다른 글

Vanilla JS 컴포넌트 설계 패턴  (0) 2025.11.10
화살표 함수와 객체  (0) 2025.11.09
API 호출  (0) 2025.10.31
async와 await  (0) 2025.10.31
Promise 객체  (0) 2025.10.31
'Frontend/JavaScript' 카테고리의 다른 글
  • Vanilla JS 컴포넌트 설계 패턴
  • 화살표 함수와 객체
  • API 호출
  • async와 await
고견
고견
개발 자국 남기기
  • 고견
    개발자국
    고견
  • 전체
    오늘
    어제
    • 분류 전체보기 (157) N
      • Frontend (29)
        • Next.js (16)
        • JavaScript (7)
      • CS (19) N
        • 자료구조 (9)
        • 알고리즘 (5)
        • 운영체제 (4) N
        • 네트워크 (1) N
      • TIL (93)
      • Dev Log (16)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    Trouble Shooting
    페이지 라우터
    바닐라 자바스크립트
    Pages Router
    알고리즘
    인터페이스
    javascript
    cs50
    Spa
    emotion diary
    트러블 슈팅
    memory
    CS
    Next.js
    C
    useState
    generic
    클래스
    제네릭
    자료구조
    함수 타입
    react
    algorithm
    ai 감성 일기장
    typescript
    타입 좁히기
    App Router
    배열
    문자열
    앱 라우터
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
고견
Vanilla JS로 클라이언트 사이드 라우팅과 검색 기능 구현하기
상단으로

티스토리툴바