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:
- Resources/assets loading. Javascript, CSS, and assets such as images, fonts, etc. are broken.
- 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).
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 inModuleScopePlugin
implemented bycreate-react-app
developers (the more info here).Set
homepage
in thepackage.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.Add
<base href="%PUBLIC_URL%/">
in yourindex.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 theindex.html
:<head> ...<base href="%PUBLIC_URL%/"> <title>React App</title> </head> <body> ...</body>
Since
%PUBLIC_URL%
will be always empty for local development serverbase 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.
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!