ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • History API
    프론트엔드/javascript 2025. 1. 4. 14:05
    728x90

    History API

    History API는 JavaScript에서 브라우저의 세션 기록을 조작하거나 탐색할 수 있는 기능을 제공합니다. 이 API는 브라우저의 주소(URL)와 기록을 변경하거나 관리하는 데 사용되며, 웹 애플리케이션의 페이지 전환을 더 부드럽고 자연스럽게 만들어 줍니다. 이를 통해 새로고침 없이 URL을 업데이트하거나 브라우저의 뒤로 가기/앞으로 가기 버튼을 제어할 수 있습니다.


    History API 주요 메서드와 속성

    1. history.pushState(state, title, url)
      • 새 기록 항목을 추가합니다.
      • 브라우저 주소(URL)를 변경하지만 페이지를 다시 로드하지 않습니다.
      • 예:
        history.pushState({ id: 1 }, 'Title', '/new-page');
    2. history.replaceState(state, title, url)
      • 현재 기록 항목을 수정합니다.
      • 새 기록을 추가하지 않고 URL을 변경합니다.
      • 예:
        history.replaceState({ id: 2 }, 'Title', '/another-page');
    3. history.back()
      • 브라우저 기록에서 이전 페이지로 이동합니다.
      • 뒤로 가기 버튼을 누른 것과 동일한 효과를 냅니다.
      • 예:
        history.back();
    4. history.forward()
      • 브라우저 기록에서 다음 페이지로 이동합니다.
      • 앞으로 가기 버튼과 동일한 동작을 합니다.
      • 예:
        history.forward();
    5. history.go(delta)
      • 지정된 delta 값만큼 앞이나 뒤로 이동합니다.
        • delta가 0: 현재 페이지 다시 로드.
        • delta가 양수: 앞으로 이동.
        • delta가 음수: 뒤로 이동.
      • 예:
        history.go(-1); // 뒤로 한 페이지
        history.go(1);  // 앞으로 한 페이지
    6. history.length
      • 현재 세션 기록에 저장된 항목의 수를 반환합니다.

    State 관리

    pushState와 replaceState는 state라는 객체를 사용해 특정 데이터를 저장할 수 있습니다. 이 데이터는 브라우저 기록에 저장되며, 페이지가 다시 로드되더라도 유지되지 않습니다.

    • 예:
      history.pushState({ page: 1 }, 'Title', '/page1');
      console.log(history.state); // { page: 1 }

    이벤트와 History API

    1. popstate 이벤트
      • 브라우저의 뒤로 가기, 앞으로 가기 버튼을 클릭하거나 history.back(), history.forward() 등이 호출되었을 때 발생합니다.
      • 예:
        window.addEventListener('popstate', (event) => {
          console.log('Location changed to:', window.location.href);
          console.log('State object:', event.state);
        });

    사용 사례

    1. 싱글 페이지 애플리케이션(SPA):
      • 새로고침 없이 URL을 업데이트하면서 상태를 관리할 수 있어 React, Vue, Angular와 같은 SPA 프레임워크에서 자주 사용됩니다.
    2. 사용자 경험 향상:
      • 주소 변경 없이 동적으로 콘텐츠를 로드하면서도 사용자가 뒤로 가기 버튼을 눌렀을 때 이전 상태를 복원할 수 있습니다.
    3. 검색 엔진 최적화(SEO):
      • URL을 변경하여 페이지 상태를 반영하면 SEO와 소셜 미디어 공유 시에도 유리합니다.

    예제 : SPA에서 History API 활용

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>History API</title>
    </head>
    <body>
        <div id="content"></div>
        <button id="pushState">pushState(새주소)</button>
        <button id="replaceState">replaceState(현재주소 갱신)</button>
        <script>
            document.querySelector('#pushState').addEventListener('click', function(){
                history.pushState({data:'push', title:'pushState'}, 'pushState', '/push');
                document.title = 'pushState';
                document.querySelector('#content').textContent = 'Content for push';
            });
            document.querySelector('#replaceState').addEventListener('click', function(){
                history.replaceState({data:'replace', title:'replaceState'}, 'replaceState', '/replace');
                document.title = 'replaceState';
                document.querySelector('#content').textContent = 'Content for replace';
            });
            window.addEventListener('popstate', function(event){
                const state = event.state;
                //history.length : 세션 기록 길이
                //history.state : 기록 스택 최상단 state 값            
                console.log('popstate',history.length, history.state);
                document.querySelector('#content').textContent = `Content for ${state.data}`;
            });
        </script>
    </body>
    </html>

    예제 : 새로고침 시 현재 URL을 유지하면서 상태를 복원 

    <!DOCTYPE html>
    <html lang="ko">
    <head>
      <meta charset="UTF-8">
      <title>History API Example</title>
      <style>
        #navigation button {
          margin: 5px;
        }
      </style>
    </head>
    <body>
      <div id="navigation">
        <button id="homeButton">Home</button>
        <button id="button1">Page 1</button>
        <button id="button2">Page 2</button>
      </div>
      <div id="content">Welcome to the Home Page</div>
    
      <script>
        // 상태 처리 함수
        function handleStateChange(event) {
          const state = event.state;
          const path = window.location.pathname;
    
          if (state) {
            // 저장된 상태가 있는 경우
            document.title = state.title;
            document.querySelector('#content').textContent = state.content;
          } else {
            // 상태가 없는 경우 URL을 기반으로 처리
            switch (path) {
              case '/page1':
                document.title = 'Page 1';
                document.querySelector('#content').textContent = 'Content for Page 1';
                break;
              case '/page2':
                document.title = 'Page 2';
                document.querySelector('#content').textContent = 'Content for Page 2';
                break;
              default:
                document.title = 'Home';
                document.querySelector('#content').textContent = 'Welcome to the Home Page';
            }
          }
        }
    
        // popstate 이벤트 리스너
        window.addEventListener('popstate', handleStateChange);
    
        // 페이지 로드 시 상태 처리
        window.addEventListener('DOMContentLoaded', () => {
          if (history.state) {
            handleStateChange({ state: history.state });
          } else {
            handleStateChange({ state: null });
          }
        });
    
        // 페이지 전환 함수
        function navigateTo(page) {
          let state, title, url, content;
    
          switch (page) {
            case 'page1':
              state = { page: 'Page1', title: 'Page 1', content: 'Content for Page 1' };
              title = 'Page 1';
              url = '/page1';
              content = 'Content for Page 1';
              break;
            case 'page2':
              state = { page: 'Page2', title: 'Page 2', content: 'Content for Page 2' };
              title = 'Page 2';
              url = '/page2';
              content = 'Content for Page 2';
              break;
            default:
              state = { page: 'Home', title: 'Home', content: 'Welcome to the Home Page' };
              title = 'Home';
              url = '/';
              content = 'Welcome to the Home Page';
          }
    
          // 상태 기록 및 URL 변경
          history.pushState(state, title, url);
    
          // UI 업데이트
          handleStateChange({ state: state });
        }
    
        // 버튼 클릭 이벤트 리스너 설정
        document.querySelector('#button1').addEventListener('click', () => navigateTo('page1'));
        document.querySelector('#button2').addEventListener('click', () => navigateTo('page2'));
        document.querySelector('#homeButton').addEventListener('click', () => navigateTo('home'));
      </script>
    </body>
    </html>

    해당 예제를 제대로 동작시키려면 서버 측에서 모든 요청을 동일한 HTML 파일로 라우팅해야 합니다. 예를 들어, 로컬에서 테스트할 경우 http-server나 live-server와 같은 도구를 사용하여 모든 경로를 index.html로 리다이렉트하도록 설정해야 합니다.

    브라우저가 /page1이나 /page2 경로로 직접 접근할 때도 index.html을 반환하여 클라이언트 측 라우팅이 가능하도록 합니다.

    npm install -g live-server
    live-server --entry-file=index.html

    제약 사항

    1. 크로스 도메인 제한:
      • 다른 도메인의 URL로 이동할 수 없습니다.
    2. 검색 엔진 크롤링:
      • 검색 엔진 크롤러는 History API로 변경된 페이지를 제대로 인식하지 못할 수 있습니다. 이를 보완하려면 서버 사이드 렌더링(SSR) 또는 동적 URL 핸들링을 고려해야 합니다.
    3. 브라우저 지원:
      • 대부분의 현대 브라우저에서 지원되지만, 일부 구형 브라우저에서는 제한될 수 있습니다.

    History API를 활용하면 브라우저 세션을 유연하게 관리하고 사용자 경험을 향상할 수 있습니다. SPA 개발 시 특히 유용합니다.

     

     


     

     

    SPA 새로고침 구현

     

    위 예제에서 live-server를 이용해 새로고침시 현재URL을 유지하는 것을 구현해봤습니다.

    한 걸음 더 나아가 해당 현상의 원인과 해결방법을 살펴보겠습니다.

     

    SPA(Single Page Application)에서 history API를 사용하여 URL 경로를 관리하는 경우, 서버가 해당 경로에 대한 요청을 처리하지 못할 때 "Cannot GET /경로" 오류가 발생할 수 있습니다. 그 이유는 다음과 같습니다:

    원인

    history API를 사용하면 브라우저의 URL이 변경되지만, 실제로는 새로운 HTML 파일을 요청하지 않고 JavaScript를 통해 클라이언트 측에서 경로를 처리합니다.

    그러나 사용자가 URL을 새로고침하거나 해당 URL로 직접 접근하면, 브라우저는 서버에 해당 경로(/경로)로 HTML 파일을 요청합니다.

    서버가 SPA 구조를 이해하지 못한 채 특정 경로(/경로)에 대해 정적 파일을 제공하지 않으면, "Cannot GET /경로"라는 에러가 발생합니다. 서버는 SPA 라우팅 로직을 알지 못하기 때문에 기본적으로 index.html을 반환해야 하지만, 이를 처리하지 않아서 문제가 생깁니다.


    해결 방법

    1. 서버에서 모든 요청을 index.html로 리다이렉트

    서버 설정을 수정하여 SPA 라우팅을 지원하도록 합니다. 서버가 특정 경로를 찾지 못하면 기본적으로 index.html을 반환하도록 설정합니다.

    예시: Express.js 서버

    const express = require('express');
    const path = require('path');
    const app = express();
    
    // 정적 파일 서빙
    app.use(express.static(path.join(__dirname, 'build')));
    
    // 모든 요청에 대해 index.html 반환
    app.get('*', (req, res) => {
      res.sendFile(path.join(__dirname, 'build', 'index.html'));
    });
    
    app.listen(3000, () => {
      console.log('Server is running on port 3000');
    });
     

    2. 웹 서버 설정 변경

    운영 환경에서 사용하는 웹 서버가 Nginx나 Apache라면, SPA를 지원하도록 설정 파일을 수정합니다.

    Nginx 설정

    server {
      listen 80;
      server_name example.com;
    
      root /path/to/your/spa;
    
      index index.html;
    
      location / {
        try_files $uri /index.html;
      }
    }
     

    Apache 설정

    .htaccess 파일을 수정하여 모든 경로를 index.html로 리다이렉트:

    <IfModule mod_rewrite.c>
      RewriteEngine On
      RewriteBase /
      RewriteRule ^index\.html$ - [L]
      RewriteCond %{REQUEST_FILENAME} !-f
      RewriteCond %{REQUEST_FILENAME} !-d
      RewriteRule . /index.html [L]
    </IfModule>
    

     


    3. 프론트엔드 라우팅에 Fallback 설정

    React Router 또는 Vue Router를 사용하는 경우, 잘못된 경로를 처리하는 fallback을 설정합니다.

    React Router

    import { BrowserRouter, Routes, Route } from 'react-router-dom';
    
    function App() {
      return (
        <BrowserRouter>
          <Routes>
            <Route path="/" element={<Home />} />
            <Route path="/about" element={<About />} />
            {/* Fallback route */}
            <Route path="*" element={<NotFound />} />
          </Routes>
        </BrowserRouter>
      );
    }
     

    결론

    이 문제는 서버가 SPA의 클라이언트 라우팅에 대해 알지 못하기 때문에 발생합니다. 서버 설정을 수정하여 모든 경로를 index.html로 리다이렉트하면 해결할 수 있습니다. 운영 환경에 맞는 서버 설정을 적용하세요.

     

     

    https://developer.mozilla.org/ko/docs/Web/API/History

     

    History - Web API | MDN

    History 인터페이스는 브라우저의 세션 기록, 즉 현재 페이지를 불러온 탭 또는 프레임의 방문 기록을 조작할 수 있는 방법을 제공합니다.

    developer.mozilla.org

     

    728x90

    '프론트엔드 > javascript' 카테고리의 다른 글

    Node.js vs Express.js  (0) 2025.01.05
    Fetch API  (0) 2025.01.04
    require  (2) 2025.01.03
    PerformanceAPI  (0) 2024.12.31
    Querystring  (0) 2024.12.29
Designed by Tistory.