Search code examples
reactjsnginxcreate-react-app

How to host multiple Create-React-App production builds with NGINX, each in a separate directory (without Docker)


I'm trying to find a way how to host multiple (currently 2) React applications, each created with CRA. Both apps should run behind a single instance of NGINX and be accessible in a separate directory:

app1 -> http://localhost/app1

app2 -> http://localhost/app2

The 2 React applications where created like this:

npx create-react-app app1
npx create-react-app app2

To host both React applications using NGINX, the production build of each CRA app (npm run build) is copied to NGINX static directory /var/www/app[1|2]

# App1
npm run build
cp build/* /var/www/app1

# App2
npm run build
cp build/* /var/www/app2

This is how NGINX can be setup to host a single CRA app:

# /etc/nginx/conf.d/default.conf
server {
    listen 80;
    listen [::]:80;

    server_name localhost;

    location / {
        root /var/www/app1;
        index index.html;
    }
}

Now I'm trying to extend this example so both CRA apps are hosted by one NGINX.

Which modifications are necessary, both to NGINX site.conf and each React application itself?

I pushed a code showing my (incomplete) example above to Github for reference: https://github.com/wolkenarchitekt/multiple-cra-apps-behind-nginx. The code is using docker-compose for simplicity, but in the end the whole stack should run without Docker, so running separate Docker services for each React app is not an option.


Solution

  • In order to run both React apps on the same domain but different subfolders, you should just need two location blocks:

    root /var/www;
    location /app1/ {
            index index.html;
        }
    location /app2/ {
            index index.html;
        }
    
    

    The necessary changes in the React apps usually include:

    • changing the homepage field in the package.json as described here
    • (only applies if react-router is used, older versions should work similarly) change the basename for BrowserRouterto your subdirectory: <BrowserRouter basename='/app1'>

    EDIT:

    After checking out your repo (before you pushed the new branch) I just got it working with the following Dockerfile (way worse for development since every code change requires a rebuild, I just like multistage builds and this would be more suited for a production environment):

    FROM node:16 as builder1
    WORKDIR /var/app
    COPY app1/package.json .
    COPY app1/package-lock.json .
    RUN npm i
    COPY app1/public/ ./public
    COPY app1/src/ ./src
    RUN npm run build
    
    FROM node:16 as builder2
    WORKDIR /var/app
    COPY app2/package.json .
    COPY app2/package-lock.json .
    RUN npm i
    COPY app2/public/ ./public
    COPY app2/src/ ./src
    RUN npm run build
    
    
    FROM nginx:mainline-alpine
    COPY ./nginx/nginx.conf /etc/nginx/nginx.conf
    COPY ./nginx/site.conf /etc/nginx/conf.d/default.conf
    COPY --from=builder1 /var/app/build/ /var/www/app1
    COPY --from=builder2 /var/app/build/ /var/www/app2
    

    The following site.conf worked for me:

    server {
        listen 80;
        listen [::]:80;
    
        server_name localhost;
        root /var/www;
    
        location /app1 {
            index index.html;
        }
        location /app2 {
            index index.html;
        }
    }
    

    I just had to change homepage to /app1 and /app2 respectively, change app2/App.js to actually output App2, and then

    docker build . -t testreactmultiple:latest
    docker run -it -p 3000:80 docker.io/library/testreactmultiple:latest  
    

    I hope this helps you reproduce the working container that errors when the index is requested but works fine when /app1 or /app2 is requested.