79

I have classic web application rendered on server. I want to create admin panel as single page application in React. I want to server admin panel from https://smyapp.example.com/admin/. I try to use create-react-app but it assumes that i serve SPA from root URL. How should I configure create-react-app to serve app from "admin" subdirectory? In documentation I found "homepage" property but if I properly understand it requires complete url. I can't give complete url because my app is deployed in few environments.

guest
  • 1,696
  • 4
  • 20
  • 31
  • Can you elaborate on "I can't give complete url because my app is deployed in few environments"? – Gabriel Bleu Mar 22 '18 at 13:30
  • @GabrielBleu In docs I found something like "homepage": "http://mywebsite.com/subdirectory", but I don't want to give full domain name here. – guest Mar 22 '18 at 13:32
  • As routing can be handled by `router` `basename`, I think this url is only for bundles, maybe you can link to a shared one like `statics.mywebsites.com` ? – Gabriel Bleu Mar 22 '18 at 13:41

9 Answers9

82

In addition to your requirements, I am adding mine:

  • It should be done by CD, through an env variable.
  • If I need to rename the subdirectory, I should only have to change the env variable.
  • It should work with react-router.
  • It should work with scss (sass) and html.
  • Everything should work normally in dev mode (npm start).

I also had to implement it in Angular2+ project not long ago, I found it harder to implement in React then in Angular2+ where you are good to go with ng build --base-href /<project_name>/. source


Short version

  1. Before building, set PUBLIC_URL env variable to the value of your subdirectory, let use /subdir for example. You can also put this variable into your .env.production (in case you do not have that file you can check the doc)
  2. In public/index.html add the base element bellow, this is for static files like images.
<base href="%PUBLIC_URL%/">
  1. Also in public/index.html, if you have custom link element, make sure theyre are prefixed with %PUBLIC_URL% (like manifest.json and favicon.ico href).
  2. If you use BrowserRouter, you can add basename prop:
<BrowserRouter basename={process.env.PUBLIC_URL} />
  1. If you use Router instead, because you need access to history.push method, to programmatically change page, do the following:
// history.tsx
import {createBrowserHistory} from 'history';

export default createBrowserHistory({ basename: process.env.PUBLIC_URL });
<!-- Where your router is, for me, App.tsx -->
<Router history={history}>
  ...
</Router>
  1. Use relative links inside your elements
<!-- "./assets/quotes.png" is also ok, but "/assets/quotes.png" is not -->
<img src="assets/quotes.png" alt="" />
  1. Move your background-image links from scss to jsx/tsx files (note that you may not need to do that if you use css files):
/*remove that*/
background-image: url('/assets/background-form.jpg');
<section style={{backgroundImage: `url('assets/background-form.jpg')`}}>
...

You should be done.


Additional informations

I preferred to use PUBLIC_URL instead of homepage in package.json because I want to use env variable set on gitlab to set the subdir. Relevant resources about the subject:

PUBLIC_URL override homepage, and PUBLIC_URL also take the domain name, if you provide one. If you set only homepage, PUBLIC_URL will be set to the value of homepage.


If you do not want to use a base element in your index.html (I would not know why), you will need to append process.env.PUBLIC_URL to every link yourself. Note that if you have react-router with a base element, but have not set basename prop, you will get a warning.


Sass won't compile with an incorrect relative path. It also won't compile with correct relative path to your ../public/assets folder, because of ModuleScopePlugin restrictions, you can avoid the restriction by moving your image inside the src folder, I haven't tried that.


There seem to be no way of testing relative path in development mode (npm start). see comment


Finnaly, theses stackoverflow link have related issues:

Ambroise Rabier
  • 3,636
  • 3
  • 27
  • 38
  • 1
    This should be the accepted answer. Using the environment variable is much better than package.json – Scaraux Nov 18 '19 at 19:57
  • This worked fine for me in dev but not in production. I have a weird setup though where I can't serve from / which might have been messing with the static/asset files. – Powersource Mar 02 '21 at 14:55
  • Using .env files was actually a great piece of advice for running the app both in dev and production environments without any pain – Arthur Z. Mar 05 '21 at 14:42
  • Thanks for the detailed answer, but I don't really think this is a "short" version! – rodorgas Dec 16 '21 at 20:08
51

For create-react-app v2 and react-router v4, I used the following combo to serve a production (staging, uat, etc) app under "/app":

package.json:

"homepage": "/app"

Then in the app entry point:

 <BrowserRouter basename={process.env.PUBLIC_URL}>
  {/* other components */}
 </BrowserRouter>

And everything "just works" across both local-dev and deployed environments. HTH!

Greg Haygood
  • 958
  • 8
  • 11
  • What about DRY (Do not repeat yourself)? In this case you have your public url in two places.. – Liga Jan 31 '20 at 07:10
  • I needed `` in public/index.html as well to make the assets work - and assets use relative urls (like ``). – kjetilh Feb 24 '23 at 11:59
31

You should add entry in package.json for this.

Add a key "homepage": "your-subfolder/" in your package.json All static files will be loaded from "your-subfolder"

If there is no subfolder and you need to load from same folder you need to add the path as "./" or remove the entire line which has "homepage": "xxxxxxxxxx"

"homepage": "./"

From the official docs

By default, Create React App produces a build assuming your app is hosted at the server root. To override this, specify the homepage in your package.json, for example:

"homepage": "http://mywebsite.com/relativepath",

Note: If you are using react-router@^4, you can route <Link>s using the basename prop on any <Router>.

From here and also check the official CRA docs

kiranvj
  • 32,342
  • 7
  • 71
  • 76
  • 1
    Spent an entire day to deploy a CRA build, finally figured out the solution - thanks to this answer! – roshnet Sep 03 '21 at 17:43
11

To get relative URLs you can build the app like this:

PUBLIC_URL="." npm run build
wieselchen
  • 123
  • 1
  • 4
2

Maybe you could use react-router and its relative basename parameter which allows you to serve your app from a subdirectory.

basename is the base URL for all locations. If your app is served from a sub-directory on your server, you’ll want to set this to the sub-directory. A properly formatted basename should have a leading slash, but no trailing slash.

For instance:

<BrowserRouter basename="/calendar"/>

So <Link to="/today"/> will render <a href="/calendar/today">

See: https://reacttraining.com/react-router/web/api/BrowserRouter/basename-string

Dharman
  • 30,962
  • 25
  • 85
  • 135
tmaz
  • 31
  • 3
2

put in package.json something like this:

"homepage" : "http://localhost:3000/subfolder",

and work fine on any public or local server. Of course, subfolder must be your folder.

ristepan
  • 45
  • 4
1

In our case, we did everything as described in Ambroise's answer, but got a blank page in production with no errors or warnings in the console. It turned out to be a problem with BrowserRouter - we got it working by setting BrowserRouter's basename like so:

<BrowserRouter basename="/our_subfolder/" />
oggy
  • 46
  • 4
0

I was facing the similar kind of issue. Need to serve react app from Godaddy hosting. let's say for example the context path should be /web ie:- http://example_123.com/web Following changes works fine for me with React 18.2.0 , react-router-dom: 6.11.0

  1. set homepage in package.json to /web ie: homepage: "/web"

  2. update basename in BrowserRouter to process.env.PUBLIC_URL

    <BrowserRouter basename={process.env.PUBLIC_URL}> 
      {/*Your components*/} 
    </BrowserRouter>
  1. build app and put your build inside /web folder in public_html ie: /public_html/web

  2. on godaddy inside public_html created index.html and redirect that to /web

<html> <head>

<title>welcome to example_123</title> 

<!--<meta http-equiv="refresh" content="0.2; URL=web/index.html"> -->
<meta name="keywords" content="automatic redirection"> 

</head> 

<body> </body> 

</html>

Bonus

If want to put your content inside /web and don;t want context path. follow below steps :-

  1. set homepage to /web in package.json
  2. put your build inside /web in public_html
  3. just move index.html from /web to publi_html ie.: put index.html generated by build inside public_html and all other files in /web
Ayush Sharma
  • 69
  • 2
  • 5
-2

You can specify the public path in your webpack configuration along with use of react route basepath.

Link to Public Path: https://webpack.js.org/guides/public-path/

Note that public path will be both leading and trailing slashes / to be valid.