Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Serving multiple react apps with client-side routing in Express

I have different software products for one single service, which needs to be deployed to a single server. The clients are built with react, with a build setup by create-react-app, while the server runs Node.js and Express.

When I serve a single application from the server it is done the following way:

// App.js
// ...
// Entry point for data routes (API)
app.use('/data', indexRoute);

if(process.env.NODE_ENV !== 'development') {
  app.use(express.static(path.join(__dirname, 'build-client')));

  app.get('/*', function(req, res) {
    return res.sendFile(path.resolve( __dirname, 'build-client' , 'index.html'));
  });
}

I want to be able to serve multiple apps from the server. How should I do that?

What I tried is to wire in different static paths for the assets and separate the clients with different names, although it did not work. Like this:

// App.js
// ...
// Entry point for data routes (API)
app.use('/data', indexRoute);

if(process.env.NODE_ENV !== 'development') {
  app.use(express.static(path.join(__dirname, 'build-client')));
  app.use(express.static(path.join(__dirname, 'build-admin')));

  app.get('/client/*', function(req, res) {
    return res.sendFile(path.resolve( __dirname, 'build-client' , 'index.html'));
  });
  app.get('/admin/*', function(req, res) {
    return res.sendFile(path.resolve( __dirname, 'build-client' , 'index.html'));
  });
}

I have also tried to do it this way, but Express throw Error: No default engine was specified and no extension was provided:

if(process.env.NODE_ENV !== 'development') {
  // Admin paths
  app.use('/admin', express.static(path.join(__dirname, 'build-admin')));
  app.get('/admin/*', function(req, res) {
    return res.sendFile(path.resolve( __dirname, 'build-admin' , 'index.html'));
  });

  // Site paths
  app.use('/', express.static(path.join(__dirname, 'build-client')));
  app.get('/*', function(req, res) {
    return res.sendFile(path.resolve( __dirname, 'build-client' , 'index.html'));
  });
}

How could I accomplish this or something similar?

like image 783
sznrbrt Avatar asked Nov 21 '17 15:11

sznrbrt


People also ask

How do you run react and Express concurrently?

Now we can run “npm run dev” to run the server and client concurrently. At the console of the chrome developer tools, we will see a cors error message. CORS is a node. js package for providing a Connect/Express middleware that can be used to enable CORS with various options.

Can I use Express and react together?

But if you just remember that React isn't an application, but a set of files, then you'll have no problem integrating Express with React in any way you'd like. One simple way is to use Express's static file server to server files to the browser that contain your React app.

Can you use Nextjs with Express?

js doesn't replace Express and you can use them together. Next. js uses some Node. js features, mainly for front-end development, while ExpressJS is used only for back-end development.

Can Express and react on same port?

Neither react nor node use any network ports as they have no networking capabilities themselfs. It's the web server (e.g. express js) which uses node as the runtime environment or the database server that use ports. You can serve your assets (react app, html, css) from the same web server.


2 Answers

After some tinkering I was able to achieve this without using virtual hosts. I used the first idea you gave in the question, except I left the main app at the root (i.e. /).

// when going to `/app2`, serve the files at app2/build/* as static files
app.use('/app2', express.static(path.join(__dirname, 'app2/build')))
// when going to `/`, serve the files at mainApp/build/* as static files
app.use(express.static(path.join(__dirname, 'mainApp/build')))


// These are necessary for routing within react
app.get('app2/*', (req, res) => {
  res.sendFile(path.join(__dirname + '/app2/build/index.html'))
})

app.get('*', (req, res) => {
  res.sendFile(path.join(__dirname + '/mainApp/build/index.html'));
});

After this, I went into mainApp/package.json and added

"proxy": "http://localhost:4141"

:4141 is the port that the express server is running on. This line will make calls to fetch('/some/route') go back to the server instead of into your react app itself.

Finally, we go to app2/package.json and add

"proxy": "http://localhost:4141/app2",
"homepage": "/app2"

I believe that the key here is the "homepage" key. The way I understand it, when react starts it searches for some static files at its homepage, and without the "homepage" piece I was only able to get either a blank white screen or the mainApp.

I hope this helps someone out there!


EDIT

I have since changed from serving my create-react-apps through my express server to serving them through netlify. Now I don't need to worry about this express setup, or the homepage key in package.json. The express server lives by itself, and the react apps can still both use the same api, and deployment is much easier. Setup with netlify is trivial.

like image 156
Danny Harding Avatar answered Oct 03 '22 23:10

Danny Harding


After struggling for a while with this problem I've found a possible solution without compromising the original setup.

We used Express vhost package to setup handling of requests through virtual domains.

When you create your app instance, you should initialize as many apps with express as you want to expose separately (in our case its three separate apps plus the original app instance)

// Create an express instance
const app = express();
const appAdmin = express();
const appClient = express();
const appVendor = express();

After that you need to install vhost and import it. Then with specifying the static folder for each app you can handle serving the static files separately, while the remaining part deals with handling the request for the given subdomains respectively.

  appAdmin.use(express.static(path.join(__dirname, 'build-admin')));
  appClient.use(express.static(path.join(__dirname, 'build-client')));
  appVendor.use(express.static(path.join(__dirname, 'build-vendor')));

  appAdmin.use((req, res, next) => {
    return res.sendFile(path.resolve( __dirname, 'build-admin' , 'index.html'));
  });

  appClient.use((req, res, next) => {
    return res.sendFile(path.resolve( __dirname, 'build-client' , 'index.html'));
  });

  appVendor.use((req, res, next) => {
    return res.sendFile(path.resolve( __dirname, 'build-vendor' , 'index.html'));
  });

  app.use(vhost('domain.com', appClient));
  app.use(vhost('www.domain.com', appClient));
  app.use(vhost('a.domain.com', appAdmin));
  app.use(vhost('b.domain.com', appVendor));

Don't forget to add the desired subdomains in your domain's DNS registry. Example:

...records
CNAME   vendor  @
CNAME   admin   @
like image 42
sznrbrt Avatar answered Oct 03 '22 23:10

sznrbrt