Author avatar

Vivek Kumar

Deploying React App with React-router to Gh-pages

Vivek Kumar

  • Jan 13, 2020
  • 14 Min read
  • 42 Views
  • Jan 13, 2020
  • 14 Min read
  • 42 Views
Web Development
React

Introduction

The creation of a React application, done in the correct order, can provide numerous benefits and services. There are certain precautionary measures you should take in the initial stages of application development. This is essential so that the final product can function in the real world and render optimum performance without technical glitches. Here, we are going to discuss a critical measure related to React app where the application is deployed to the GitHub (gh) pages with react-router. As we all realize, the fact that the server hosting services offered by GitHub enable you to publish pages from custom domains also ensures that the pages are published according to its terms and conditions.

The objective of this guide is to help you learn the process of deploying React app with react-router to gh-pages (GitHub pages) in the correct manner. For those who are not familiar with the role of GitHub pages in React app functioning, let's look briefly at how GitHub pages work with React.

GitHub Pages

GitHub pages is basically a static website hosting service designed to host organizational, personal, or rather simple or advanced project pages directly through a GitHub repository. One can easily build and publish gh-pages sites with the help of Jekyll Theme Chooser. If your priority is to work locally, then you can apply for GitHub Desktop, available for both Windows and Mac, or use command line.

GitHub pages are highly conducive to custom domains, and that is why you can host a static site on gh-pages without involving server cost. Most people prefer GitHub services because, despite the free services, deployments are straight from Git without the involvement of any third party. Yes, you read that correctly! The services are streamed directly.

A Case Study

Now that we've briefly gone through what gh-pages offer and how a person with a static-page site can make use of them, it’s time to look at a standard case of an app deployment failure and its solution. If you are deploying your web application to GitHub pages and getting a failure or error message, it is important to run a few tests to understand where the actual problem is resting.

Restricting ourselves to the objective of this guide, let's look at a practical scenario. Consider the development of the Reddit Viewer webpage developed by James Ives [GitHub profile], which, according to him, serves the following purpose:

A single page application built with React and Redux that allows you to view content from Reddit.

Reddit Viewer Homepage Imgur

Searching For Results Under r/accounting Imgur

While working on his webpage, James put up a question on StackOverflow about this very topic.

The case that we are about to study deals with a situation where everything is working well locally, but not when transferred to gh-pages. FYI, here is the main body of the index.html file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <meta name="author" content="James Ives">
    <meta name="description" content="Reddit Viewer SPA">
    <meta name="keywords" content="react, redux, reddit, api, spa">
    <title>React Reddit Viewer</title>

    <link rel="icon" type="image/png" href="[ABSOLUTE PATH]/assets/favicon-32x32.png" sizes="32x32" />
    <link rel="icon" type="image/png" href="[ABSOLUTE PATH]/assets/favicon-16x16.png" sizes="16x16" />

    <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
    <link rel="stylesheet" href="[ABSOLUTE PATH]/style/materialize.css">
    <link rel="stylesheet" href="[ABSOLUTE PATH]/style/style.css">
    <script src="[ABSOLUTE PATH]/scripts/jquery.js"></script>
    <script src="[ABSOLUTE PATH]/scripts/materialize.min.js"></script>

  </head>
  <body>
    <div class="wrapper"></div>
  </body>
  <script src="./bundle.js"></script>
</html>
html

The code holds basic metadata information along with invoking few Javascripts and CSS scripts.

A random thought suggests that gh-pages can put the application in a sub-folder and it is the index.html file which is causing the routing issues. To follow up the scenario, the routes created by him are given below saved under index.js file:

1
2
3
4
5
6
7
8
9
export default (
    <Route path="/" component={App}>
        <IndexRoute component={Search} />
        <Route path="r/:sub" component={Viewer} />
        <Route path="r/:sub/:filter" component={Viewer} />
        <Route path="r/:sub/:filter/:search" component={Viewer} />
        <Route path="/search" component={Search} />
    </Route>
)
jsx

The code holds the route path to few of the Reddit subcomponents.

In short, here the problem is the change in the base path each time the routes are visited directly, and it hampers the functioning of single-page application as the file path relativity differs.

Identifying The Mistake and Working on the Solution

The problem arises due to the lack of information on how gh-pages operate, which becomes a hurdle for people deploying the React app with react-router to gh-pages.

The mistake people occasionally commit in the index.html file is that they do not have absolute control over the entire domain (here, gh-pages), and therefore there is no use in applying absolute URL. It is better to replace these URLs with the relative ones as shown below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="theme-color" content="#000000">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <meta name="author" content="James Ives">
    <meta name="description" content="Reddit Viewer single page application built with React and Redux">
    <meta name="keywords" content="react, redux, reddit, api, spa">

    <link rel="icon" type="image/png" href="%PUBLIC_URL%/assets/favicon-32x32.png" sizes="32x32" />
    <link rel="icon" type="image/png" href="%PUBLIC_URL%/assets/favicon-16x16.png" sizes="16x16" />

    <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
    <link rel="stylesheet" href="%PUBLIC_URL%/style/materialize.css">
    <link rel="stylesheet" href="%PUBLIC_URL%/style/style.css">
    <script src="%PUBLIC_URL%/scripts/jquery.js"></script>
    <script src="%PUBLIC_URL%/scripts/materialize.min.js"></script>
    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">

    <title>Reddit Viewer</title>
  </head>
  <body>
    <noscript>
      You need to enable JavaScript to run this app.
    </noscript>
    <div id="root"></div>
  </body>
</html>
html

The above code is the modification of the index.html code provided above. Here, the absolute paths are replaced with relative paths. The updated code can be found here.

Additionally, there is always going to be a problem in the deployment if we are consistently applying the browserHistory under the index.js file. Here, the issue is linked with the problem of the absence of control of the domain; you cannot expect the desired results. It is advisable to go with hashHistory instead of browerHistory as shown below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import {createStore, applyMiddleware} from 'redux';
import {Router, hashHistory} from 'react-router';
import thunk from 'redux-thunk';

import routes from './routes';
import reducers from './reducers';

const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);

ReactDOM.render(
  <Provider store={createStoreWithMiddleware(reducers)}>
    <Router history={hashHistory} routes={routes} />
  </Provider>,
  document.querySelector('#root')
);
jsx

The above code presents the complete index.js file with the application of hashHistory.

Now, as you can see in the commands above, the application of hashHistory can simply solve the issue, and it is a lot easier to link with the router history and fetch results accordingly. If due to any specific reason the application of browserHistory is a must for you, then obviously you ought to deal with 403 and 404 redirections via your server provider. It becomes a tiresome job if you do not have control over the domain.

GitHub Pages and Relative Paths

We have observed in our case study that the original problem was linked to the proper use of relative paths. To understand the concept more clearly, let's consider one more scenario in which you are creating a gh-pages branch for a project on GitHub. You use a text editor to create the website locally, and while pushing it to GitHub, all the links to Javascripts, images, and others are listed invalid.

To be more specific, we can consider a code snippet from the head section of the HTML file so created:

1
<link href="assets/css/common.css" rel="stylesheet">
html

The above code points to the common.css file and works perfectly fine when executed locally, but fails on GitHub and asks for:

1
http://[user].github.io/assets/css/common.css

instead of:

1
http://[user].github.io/[repo]/assets/css/common.css.

Now, such a problem has been creating issues for quite some time, and as time has advanced, various solutions have come into the picture.

Solution 1

When you include:

1
<link href="assets/css/common.css" rel="stylesheet">
html

in an HTML document at http://www.foo.com/bar/doc.html, the link to assets/css/common.css will get resolved by appending it to the prefix of the URL of the HTML document without the last part of the path (without doc.html). The link will resolve to http://www.foo.com/bar/assets/css/common.css, and not to http://www.foo.com/assets/css/common.css.

Solution 2

Matt Swensen [GitHub profile] in Jekyll issue #332 illustrates the proper use of a standard URL for GitHub pages using Jekyll.

Sometimes it’s nice to preview your Jekyll site before you push your gh-pages branch to GitHub. However, the subdirectory-like URL structure GitHub uses for Project Pages complicates the proper resolution of URLs. Here is an approach to utilizing the GitHub Project Page URL structure (username.github.io/project-name/) while maintaining the ability to preview your Jekyll site locally.

  1. In _config.yml, set the baseurl option to /project-name —note the leading slash and the absence of a trailing slash.
  2. When referencing JS or CSS files, do it like this: {{ site.baseurl}}/path/to/css.css—note the slash immediately following the variable (just before “path”).
  3. When doing permalinks or internal links, do it like this: {{ site.baseurl }}{{ post.url }}—note that there is no slash between the two variables.
  4. Finally, if you’d like to preview your site before committing/deploying using jekyll serve, be sure to pass an empty string to the --baseurl option, so that you can view everything at localhost:4000 normally (without /project-name at the beginning): jekyll serve --baseurl ''

This way you can preview your site locally from the site root on localhost, but when GitHub generates your pages from the gh-pages branch all the URLs will start with /project-name and resolve properly.

Conclusion

In this guide, you have learned to deploy React app with react-router to gh-pages, avoiding setbacks that may arise due to the use of absolute URLs. We discussed what steps not to take when you do not have absolute control over the domain and how to opt for a better alternative than the browserHistory in order to avoid error messages and access the router history conveniently. I hope the elements mentioned in the guide are worth your time and save you a lot of pain in the long run.

0