45

I have a React app I've been developing on my localhost. I want to copy it to a server into a subdirectory called vensa.

My webpack config file looks like this..

const ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
  entry: [
    './src/index.js'
  ],
  output: {
    path: 'build',
    filename: 'bundle.js'
  },
  module: {
    loaders: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel'
      },
      {
        test: /\.scss$/,
        loader: ExtractTextPlugin.extract('style', 'css!sass')
      },
      {
        test: /\.css$/,
        loader: ExtractTextPlugin.extract('style', 'css')
      },
      {
        test: /\.(png|eot|svg|ttf|woff(2)?)(\?v=\d+\.\d+\.\d+)?/,
        loader: 'url'
      }
    ]
  },
  plugins: [
    new ExtractTextPlugin('vensa-dashboard.css')
  ],
  devServer: {
    historyApiFallback: true,
    contentBase: './build'
  }
};

The index.html file looks like this...

<!DOCTYPE html>
<html>
<head>
  <title>Vensa Development Test</title>
  <link rel="stylesheet" href="/vensa-dashboard.css">
</head>
<body>
  <div class="container"></div>
  <script src="/bundle.js"></script>
</body>
</html>

and my routes.js file is...

import React from 'react';
import { Route, IndexRoute } from 'react-router';
import VensaDashboard from './components/VensaDashboard';
import Inbox from './components/Inbox';
import Todo from './components/Todo';
import Home from './components/Home';

export default (
  <Route path="/" component={VensaDashboard}>
    <IndexRoute component={Home} />
    <Route path="/message" component={Inbox} />
    <Route path="/todo/:todoPage" component={Todo} />
  </Route>
);

However, if I do just run webpack -p and copy the 3 files over to that subdirectory, it doesn't work as the root path is / and it can't find the js and css files. I'm not sure what (the best way) to change in (probably one or all of these 3 files) to get it to work in a subdirectory?

The full source code of the app is here in case that helps.

Thanks!

magician11
  • 4,234
  • 6
  • 24
  • 39
  • Almost seems easier to create a subdomain instead for the app so I don't have to play around with the config of the app?! – magician11 May 31 '16 at 05:27
  • Simply add - subdirectory path in homepage: "/subdirectory" inside package.json. and add a basename in the router if required. ref link - https://www.fullstacktutorials.com/deploy-react-app-subdirectory-10.html – Full Stack Tutorials Apr 26 '21 at 14:22

10 Answers10

56

If you are using React Router v4 you should be able to set it using basename={foobar}.

<Router history={browserHistory} basename={'foobar'}>
  <Route path="/" component={App} />
</Router>

Link to the docs: https://reacttraining.com/react-router/web/api/BrowserRouter

Note: if using create-react-app in a sub directory you will also want to set "homepage": "/foobar/", in your package.json file. So the production build points at the right path.

trenthogan
  • 986
  • 1
  • 9
  • 14
  • 6
    this works holy Jesus it works thanks man I searched this solution for days – sparsh turkane Jun 09 '17 at 09:39
  • @sparshturkane hey! can you please share a working example of this? I'm also stuck with this. – Dipen Dedania Jul 03 '17 at 07:34
  • @DipenDedania For instance .. renders From the docs. – trenthogan Jul 04 '17 at 09:40
  • This works for production, but I'm still searching a solution for development on port :3000, I use a workaround for instance: manually add the inclusion of the bundle with a relative path (the dev build produces a bsolute path to the root) : I added to index.html – pdem Aug 23 '17 at 10:27
  • 1
    Thanks @trenthogan, that is awesome. But as novice I was stumped as where to actually put the code as I was working from a tutorial w/out ROUTER. Here's what worked... in my index.js import React from 'react'; import ReactDOM from 'react-dom'; import './css/index.css'; import App from './App'; import * as serviceWorker from './serviceWorker'; import { BrowserRouter as Router, Route } from 'react-router-dom'; ReactDOM.render( , document.getElementById('root')); serviceWorker.unregister(); – Jc Nolan Oct 17 '19 at 23:53
  • In addition to these answers, I also added the following environment variable to my `.env.production` file so that all the static files were physically built into a subdirectory as well: `BUILD_PATH=./build/foobar`. (I also had an nginx configuration [here](https://stackoverflow.com/a/74539348/6854489) to help me with routing requests to the subdirectory correctly) – takanuva15 Nov 22 '22 at 22:15
15
  1. Add "homepage": "https://yourdomain/subdirectory/" in package.json
  2. Update the Routes, set sub directory in route,
<Router basename={'/directory-name'}>
  <Route path='/' component={Home} />
</Router>
  1. Add base in public/index.html. It helps to set path without issues.

    <base href="%PUBLIC_URL%/">

  2. make all css, js, images & all resource loading './assets/your resource.'

  3. Only look if you are using history. add basename in history

    const historyConfig = {
        basename: 'your subdirectory'
    };
    const history = createBrowserHistory(historyConfig);
Rayees Pk
  • 2,503
  • 1
  • 23
  • 19
9

In my situation, I needed to support a scenario, where there might be 0 to many subfolders with unknown names and production uses only static files.

In my App.js, I defined this simple function:

const getBasename = path => path.substr(0, path.lastIndexOf('/'));

Which I use to define the basename:

<Router basename={getBasename(window.location.pathname)}>

This works with no subfolder as well as many subfolders and it can manage reloads as well.

Ragnar
  • 4,292
  • 4
  • 31
  • 41
  • what is the value of the homepage in package.json? – Pradip Vadher Nov 24 '20 at 05:52
  • Wouldn't this only work if your sub-routes are only 1 level deep? Seems like you couldn't do something like '/app-root-dir/users/myname' since it'd make '/app-root-dir/users' the base route. – pbatey Feb 23 '21 at 17:11
3

I had to do something similar recently in order to get a react app running in a sub directory. Try the below and see how you get on.

import React from 'react';
import { Router, Route, IndexRoute, useRouterHistory  } from 'react-router';
import { createHistory } from 'history';
import VensaDashboard from './components/VensaDashboard';
import Inbox from './components/Inbox';
import Todo from './components/Todo';
import Home from './components/Home';

// specify basename below if running in a subdirectory or set as "/" if app runs in root
const appHistory = useRouterHistory(createHistory)({
  basename: "/vensa"
});

export default (
  <Router history={appHistory} />
    <Route path="/" component={VensaDashboard}>
      <IndexRoute component={Home} />
      <Route path="/message" component={Inbox} />
      <Route path="/todo/:todoPage" component={Todo} />
    </Route>
  </Router>
);

You may also have to update the path to your bundle.js file in index.html as shown below

<!DOCTYPE html>
<html>
<head>
  <title>Vensa Development Test</title>
  <link rel="stylesheet" href="/vensa-dashboard.css">
</head>
<body>
  <div class="container"></div>
  <script src="bundle.js"></script>
</body>
</html>
Rusta
  • 113
  • 6
3

Or you could use an environment variable to adjust all your links manually.

const ENV = process.env.NODE_ENV || 'local'; //development
const config = {
    publicPath: ENV !== 'production' ? '/' : '/dev/'
};
plugins: ([
        new webpack.DefinePlugin({
            'process.env.NODE_ENV': JSON.stringify(ENV),
            'process.env.config': JSON.stringify(config)
        })
})

Then one of your routes:

<Route path={process.env.config.publicPath + "account/"} component={Account} />

Then your links:

<Link class="panel-close" href={process.env.config.publicPath + "account/"} >Account</Link>

This worked great for me. Especially since I'm using preact, whose preact-router doesn't really support the basename at this time.

<Router history={browserHistory} basename={'foobar'}>
  <Route path="/" component={App} />
</Router>
lastlink
  • 1,505
  • 2
  • 19
  • 29
  • Be sure to check out the solution below that uses the `` html tag. It is cleaner than putting environment variables across your code base. – V Maharajh Oct 01 '19 at 16:37
2

Use the html-webpack-plugin to generate your final index.html with the correct bundle names automatically injected into it.

Then set output.publicPath in your webpack config to tell webpack the subdirectory your assets will be deployed to:

output: {
  path: 'build',
  publicPath: "/vensa/",
  filename: 'bundle.js'
},
Brandon
  • 38,310
  • 8
  • 82
  • 87
  • This looks great in how it auto generates an HTML file with the correct prefixed path. Do I need to change the routes as well though? I'm getting `[Error] Warning: [react-router] Location "/vensa/" did not match any routes` – magician11 May 24 '16 at 04:07
  • Yes I believe when you create the History object for your router, there's a basepath option you can pass `/vensa` and then router should work. – Brandon May 24 '16 at 14:23
  • Yes I see https://github.com/reactjs/react-router/blob/master/docs/guides/Histories.md#customize-your-history-further but not sure how to use it with browserHistory that I'm currently using. Do you know @Brandon ? – magician11 May 31 '16 at 05:23
  • 1
    Don't use the pre-created `browserHistory`. Instead create your own by calling `createHistory` yourself like the first example here: https://github.com/reactjs/react-router/blob/master/docs/guides/Histories.md#examples Use that `history` object you create instead of `browserHistory`. – Brandon May 31 '16 at 13:37
2

Adding to trenthogan's reply, we can assign basename to location.pathname

const loc = window.location || {};
<Router history={browserHistory} basename={loc.pathname || 'foobar'}>
  <Route path="/" component={App} />
</Router>

With this change, the app will work even if the subdirectory path changes in the future.

  • This actually doesn't work when you go directly to a subpage of your app, as that subpage will be assumed to be the main page and all links will be relative to /home/subpage, even though your files are at /home/js.js for example – Larzan Aug 16 '18 at 12:01
  • @Larzan: Yes Larzan you are right when we directly go to subpage its a problem, the subpage now renders the main page content. But the file links issue doesn't come here, it comes only when we repeat this behaviour one more time. Have create a [demo](https://beautiful-libra.glitch.me/subdomain) for the same. – Ramanathan Muthuraman Aug 25 '18 at 08:12
2

if you want to install in a sub-directory then you need to specify the basename here is an example.

  <Router basename="/dashboard/">

  <Route path="/login" component={Login} />

jerryurenaa
  • 3,863
  • 1
  • 27
  • 17
1

Thanks @trenthogan, that is awesome. But as novice I was stumped as where to actually put the code as I was working from a tutorial w/out ROUTER. Here's what worked for me once I got it all sussed out...

in my index.js...

import React from 'react';
import ReactDOM from 'react-dom';
import './css/index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { BrowserRouter as Router, Route } from 'react-router-dom';

ReactDOM.render(
    <Router basename={'foobar'}>
        <Route path="/" component={App} />
    </Router>, document.getElementById('root'));
serviceWorker.unregister();

Which previously looked like...

import React from 'react';
import ReactDOM from 'react-dom';
import './css/index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';

ReactDOM.render(<App />, document.getElementById('root'));
serviceWorker.unregister();
Jc Nolan
  • 450
  • 4
  • 15
0
var sourcePath = path.resolve(__dirname, 'src', 'index.js');
var buildPath = path.resolve(__dirname, 'vensa');

module.exports = {
    entry: [sourcePath],
    output: {
        path: buildPath,
        filename: '[name]-[hash].js',
        hash: true
    }
    ...
Vladimirs Matusevics
  • 1,092
  • 12
  • 21
  • Can you explain this a bit please? It looks like entry is the same in my config `'./src/index.js'` and that the path it will build into is `vensa`. What's happening with the filename and hash? Thanks @krotov – magician11 May 31 '16 at 05:14
  • Sorry, I'm using `[name]` as I use different entry points. `[hash] is replaced by the hash of the compilation.` You can see check more about webpack config [here](https://github.com/webpack/docs/wiki/configuration) @magician11 – Vladimirs Matusevics Jun 02 '16 at 12:15