arrow-rightgithublinkedinscroll-to-topzig-zag

An elegant solution of deploying React app into a subdirectory

Last Updated On

Let's start with an obvious statement: React application deployed to a subfolder will not work out of the box:

The reason for that is the path: React still tries to load its resources and assets from the root folder without accounting for a subdirectory.

In fact, here we're dealing with two types of issues:

  1. Resources/assets loading. Javascript, CSS, and assets such as images, fonts, etc. are broken.
  2. Router navigation. The default router configuration doesn't intend to work within the subdirectory.

Sidetone: in the article, I'm using word assets for all static files as images, fonts, icons, etc.

An overview of the existing (second-class) solution

Some guides suggest prepending every route and resource with process.env.PUBLIC_URL:

component.js

// modified router link
<Link to={`${process.env.PUBLIC_URL}/page-path`}></Link>

// modified link to the external resource
<img src={`${process.env.PUBLIC_URL}/img/logo.png`}/>

The downside

This solution doesn't provide a way of dealing with images in CSS/SCSS/LESS:

.my-style {
    // incorrect syntax and will not work
    background-image: url(`${process.env.PUBLIC_URL}/assets/my-background-image.png`);
}

Secondly, you might agree that the code becomes ugly and less flexible due to the fact of environment variable everywhere.

Why not use the most elegant approach specifically designed to deal with such issues? I mean Document Base URL Element.

An overview of the more elegant solution with Base URL Element

Benefits:

  • Works for all resources - Javascript, CSS/SCSS/LESS
  • Works for all assets - images, fonts, icons (including images and icons used in CSS for background images).
  • Works for ajax requests
  • Doesn't require prepending process.env.PUBLIC_URL before each URL, asset or request (although still requires converting all absolute paths to relative ones). As a result, you get much cleaner codebase without polluting it by inserting environment variable in each line.
  • Works for local dev server (the app will be served from the root) and production/staging (where the app will be served from the subfolder).

An instruction for the solution with Base URL Element

Prerequisites: This guide assumes that you build your app using create-react-app, use react router 4+ and don't want to eject (or mess with any webpack configuration).

  1. Convert all absolute links to relative ones. Base tag works only for relative URLs. The bare minimum plain HTML example:

    <head>
      ...
      <base href="/subdir/">
      <title>React App</title>
    </head>
    <body>
        <img src="./assets/logo.svg"/>
      ...
    </body>

    In other words, it's required to replace all paths that start with / to ./. Conversion should be applied to all resources and assets.

    As was mentioned, this approach can be used for ajax calls if needed. If, for any reason, you don't want to load your asset or prepend path with subdir to the URL of ajax call, leave it absolute.

    Please note: make sure all assets reside within the src folder, for example:

    demo-app
    |--node_modules
    |--public
    |--src
      |--assets
        logo.svg
        ...
      App.css
      App.js
      index.js
      registerServiceWorker.js
      ...

    If your images are outside of the src folder relative paths will not work correctly due to special restriction in ModuleScopePlugin implemented by create-react-app developers (the more info here).

  2. Set homepage in the package.json to /subdir/ :

    package.json:

    "homepage": "/subdir/"

    This setting will be used later as a %PUBLIC_URL% environment variable in a base href as well as in the router. This value is applied only for the production build (which is actually what we need) and doesn't affect our local development server.

  3. Add <base href="%PUBLIC_URL%/"> in your index.html. This tag should be placed in the <head>...</head> before any other element that uses URLs. The best place to put it right above the <title>...</title> in the index.html:

    <head>
      ...
      <base href="%PUBLIC_URL%/">
      <title>React App</title>
    </head>
    <body>
      ...
    </body>

    Since %PUBLIC_URL% will be always empty for local development server base href will be resulted in /. In a case of production build, it will be changed to /subdir/ (as described in the previous step).

    This solves the problem with asset loading.

  4. Adjust the router. Since every react-router uses history package, we need to import it and make some adjustments. First of all, install history package if you don't have it:

    npm install --save history

    In your application, add the following lines of code:

    import { createBrowserHistory } from 'history';
    
    ...
    
    export const history = createBrowserHistory({
        basename: process.env.PUBLIC_URL
    });
    
    ...

    This will prepend necessary path and make router work in each environment: local as well as production.

Conclusion

This solution offers a way to deploy your site to a subfolder at any level by using native HTML capabilities without adding environment config everywhere. It definitely worked for me and if did or didn't for you, leave your comment below. Happy coding!