[React] React SPA in GitHub Pages with BrowserRouter

김희정·2024년 3월 14일
0

React

목록 보기
6/6

💎 들어가며

Portfolio 페이지를 GitHub Pages에 올리면서 메인페이지에 너무 많은 거를 보여주려다 보니 속도 이슈가 생겨 페이지를 분리하여 보여주고자 라우팅을 결심하게 되었습니다.

이번 포스팅에서는 라우팅을 적용한 과정을 공유해보고자 합니다.

  • react-router-dom 라이브러리를 이용한 페이지 분리
  • BrowserRouter 적용을 위한 추가 설정 for GitHub Pages

1. Router in React

💡 주의

React Router dom ver. 6.22.3
최신 버전의 react-router-dom을 이용하기 때문에 설정이 조금 다를 수 있습니다.

1.1 Install

react-router-dom 패키지를 설치합니다.

# react-router-dom
npm install react-router-dom

# Typescript를 사용할 경우
npm install -D @types/react-router-dom

1.2 Router 종류

react-router-dom은 4가지 라우터를 제공합니다.

  • BrowserRouter: history API를 이용하여 MPA처럼 보여주는 라우터
  • MemoryRouter: BrowserRouter를 Test시 사용
  • HashRouter: URL에 해시(#)를 사용하는 Router
  • StaticRouter: SSR(Server-Side Rendering)시 이용하는 Router

이중에서도 특히 많이 사용되는 Router는 BrowserRouterHashRouter입니다.

이 두 Router는 react-router-dom에만 해당되는 이야기는 아닙니다. BrowserRouter는 HTML5의 history API를 사용한 방식이고, HashRouter는 URL의 hash를 사용한 방식입니다.


1.3 BrowserRouter

v6.4 Data APIs 버전부터는 createBrowerRouter, createHashRouter, createMemoryRouter, createStaticRouter 등을 이용하여 라우팅을 설정할 수 있습니다.

그 중에서도 이번포스팅에서는 BrowserRouter를 이용할 것이기 때문에 관련된 설정을 정리해보았습니다.

  • BrowserRouter는 웹 프로젝트에 권장되는 라우터입니다.
  • DOM History API를 사용하여 URL을 업데이트하고 기록 스택을 관리합니다.

default setting

// 파일 위치: src/index.html
// Routing 설정
const router = createBrowserRouter([
    {
        path: "/",
        element: <MainApp/>,
    },
    {
        path: "*",
        element: <ErrorApp/>,
    },
], {
    basename: process.env.PUBLIC_URL
});

root.render(
    <React.StrictMode>
        <RouterProvider
          router={router}
          fallbackElement={<Root/>}
        />
    </React.StrictMode>
);

createBrowserRouter를 이용하여 routing 설정을 한뒤, RouterProvider를 이용하여 router를 저장합니다.


createBrowserRouter

createBrowserRouter는 함수로 정의는 다음과 같습니다.

function createBrowserRouter(
  routes: RouteObject[],
  opts?: {
  	// 도메인의 루트가 아닌 하위 디렉터리에 배포할 수 없는 상황에 대한 앱의 기본 이름
    basename?: string; 
 	// 실험실 기능; v7에 추가될 기능들. 마이그레이션을 쉽게 하기 위해 정의됨
    future?: FutureConfig;
    // SSR(서버 사이드 렌더링)에서 사용하는 옵션
    // hydration은 서버에서 내려준 HTML이 리액트 컴포넌트로 관리되기 위한 작업
    hydrationData?: HydrationState;
	// 브라우저 devtool 플러그인과 같은 환경이나 전역 window.
    window?: Window; 
  }
): RemixRouter;

RouteObject

RouteObject는 아래와 같이 구성되어 있습니다.

const router = createBrowserRouter([
  {
    // 접근할 경로
    path: "/",
    // 보여줄 컴포넌트
    element: <Root />,
    // 로더
    loader: rootLoader,
    // 하위 컴포넌트
    children: [
      {
        path: "team",
        element: <Team />,
        loader: teamLoader,
      },
    ],
  },
]);

특이한 점은 Data API가 제공되는 createBrowserRouter에서는 loader를 통해 각 경로에 렌더링되기 전에 경로 요소에 데이터를 제공하는 로더 기능을 정의할 수 있습니다.

createBrowserRouter([
  {
    element: <Teams />,
    path: "teams",
    loader: async () => {
      return fakeDb.from("teams").select("*");
    },
    children: [
      {
        element: <Team />,
        path: ":teamId",
        loader: async ({ params }) => {
          return fetch(`/api/teams/${params.teamId}.json`);
        },
      },
    ],
  },
]);

2. React SPA in GitHub

이렇게 세팅된 React 프로젝트를 GitHub Pages에 올려도 다른 페이지로 이동할 수 없는데요, github pages에서는 BrowserRouter를 이용할 경우 추가 세팅이 필요합니다.

💡 왜 설정이 필요할까?

GitHub 페이지는 기본적으로 단일 페이지 앱을 지원하지 않습니다.

example.tld/foo와 같이 URL에 대한 새로운 페이지 로드가 있는 경우, GitHub 페이지 서버는 프런트엔드 경로가 어디에 있는지에 대해 아무것도 모르기 때문에 404를 반환합니다

2.1 404.html

404.html 생성

public 디렉터리에 404.html 파일을 생성하고, 아래와 같이 내용을 복사합니다.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Single Page Apps for GitHub Pages</title>
    <script type="text/javascript">
      // Single Page Apps for GitHub Pages
      // MIT License
      // https://github.com/rafgraph/spa-github-pages
      // This script takes the current url and converts the path and query
      // string into just a query string, and then redirects the browser
      // to the new url with only a query string and hash fragment,
      // e.g. https://www.foo.tld/one/two?a=b&c=d#qwe, becomes
      // https://www.foo.tld/?/one/two&a=b~and~c=d#qwe
      // Note: this 404.html file must be at least 512 bytes for it to work
      // with Internet Explorer (it is currently > 512 bytes)

      // If you're creating a Project Pages site and NOT using a custom domain,
      // then set pathSegmentsToKeep to 1 (enterprise users may need to set it to > 1).
      // This way the code will only replace the route part of the path, and not
      // the real directory in which the app resides, for example:
      // https://username.github.io/repo-name/one/two?a=b&c=d#qwe becomes
      // https://username.github.io/repo-name/?/one/two&a=b~and~c=d#qwe
      // Otherwise, leave pathSegmentsToKeep as 0.
      var pathSegmentsToKeep = 0;

      var l = window.location;
      l.replace(
        l.protocol + '//' + l.hostname + (l.port ? ':' + l.port : '') +
        l.pathname.split('/').slice(0, 1 + pathSegmentsToKeep).join('/') + '/?/' +
        l.pathname.slice(1).split('/').slice(pathSegmentsToKeep).join('/').replace(/&/g, '~and~') +
        (l.search ? '&' + l.search.slice(1).replace(/&/g, '~and~') : '') +
        l.hash
      );

    </script>
  </head>
  <body>
  </body>
</html>

BrowserRouter 설정

BrowserRouter를 설정한 소스를 열어줍니다. BrowserRouter 설정에 basename을 추가해줍니다.

// 파일 위치: src/index.tsx
import {createBrowserRouter, RouterProvider,} from "react-router-dom";

const router = createBrowserRouter([
  // ...라우팅설정
], {
    basename: process.env.PUBLIC_URL
});

root.render(
    <React.StrictMode>
        <RouterProvider router={router}/>
    </React.StrictMode>
);

webpack 설정

저는 webpack의 HtmlWebpackPlugin을 사용하여 html 템플릿을 만들기 때문에 CopyWebpackPlugin을 설치하여 아래와 같이 설정을 추가해주었습니다.

npm install copy-webpack-plugin --save-dev

webpack.config.js 파일을 수정합니다.

module.exports = (env, argv) => {
  return {
  	...
    plugins: [
      new CopyWebpackPlugin({
        patterns: [
          ...
          {
            from: 'template/404.html',
            to: '404.html',
          },
        ]
      })
    ]
  }
}

2.2 index.html

기존에 사용하고 있던 index.html에 아래의 스크립트를 추가합니다.

<!DOCTYPE html>
<html>
  <head>
    <script type="text/javascript">
        // Single Page Apps for GitHub Pages
        // MIT License
        // https://github.com/rafgraph/spa-github-pages
        // This script checks to see if a redirect is present 
        // in the query string,
        // converts it back into the correct url and adds it to the
        // browser's history using window.history.replaceState(...),
        // which won't cause the browser to attempt to load the new url.
        // When the single page app is loaded further down in this file,
        // the correct url will be waiting in the browser's history for
        // the single page app to route accordingly.
        (function(l) {
            if (l.search[1] === '/' ) {
                var decoded = l.search.slice(1).split('&').map(function(s) {
                    return s.replace(/~and~/g, '&')
                }).join('?');
                window.history.replaceState(null, null,
                    l.pathname.slice(0, -1) + decoded + l.hash
                );
            }
        }(window.location))
    </script>
    <!-- End Single Page Apps for GitHub Pages -->
    
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

💎 References


💎 마치며

이번 포스팅에서는 최신 버전인 react-router-dom과 GitHub Pages 세팅에 대해 정리해보았습니다.

관련하여 많은 포스팅이 있지만 react-router-dom 및 GitHub 세팅(index.html404.html)이 하위 버전인 경우가 많아 정리차원에서 포스팅을 작성하였습니다.😊

읽어주셔서 감사합니다.

profile
Java, Spring 기반 풀스택 개발자의 개발 블로그입니다.

0개의 댓글