6

Problem:

We have a CRA (Create React App) application that we need to deploy to many different environments. We're using Kubernetes to spawn these environments so we will have many of them, the number is not fixed.

The problem is that those environments must be under the same domain name and must have the same path structure. (this is a business requirement and we cannot influence it).

Example of environments:

https://www.example.com/company/app-env01/react_application/
https://www.example.com/company/app-env02/react_application/
https://www.example.com/company/app-env03/react_application/
https://www.example.com/company/app-env-foo/react_application/
https://www.example.com/company/app-env-bar/react_application/
https://www.example.com/company/app/react_application/

So the fixed structure is: https://www.example.com/company/app[-____]/react_application/

The way to make it work on a single environment would be to specify PUBLIC_URL in package.json build script:

    ...
    "build": "PUBLIC_URL=/company/app-env01/react_application react-scripts build",
    ...

This works fine for ONE environment only (env01 in this case), but we need to deploy a single build artifact (static file bundle) to MANY different environments, which have different PUBLIC_URLs.

Is this possible to achieve without building the application for every environment?

How we build the artifact:

Our deployment artifact is a Docker image, which is just Nginx serving previously built React static files.

How we perform routing:

The Browser is hitting our Kubernetes Ingress, which strips the path away and forwards the request to a Docker Container (in a K8S Pod) which is running Nginx. Nginx is serving the CRA static files under the root directory /.

Example:

Browser
  ↓
https://www.example.com/company/app-env01/react_application/
  ↓
Ingress
  /
  ↓
Nginx
  ↓
Serves the CRA static files

This works fine, the problem (to me at least it seems) is in React Router. We are setting the basename dynamically, and it is being set correctly (https://reactrouter.com/web/api/BrowserRouter/basename-string). The problem (I assume) is in this PUBLIC_URL. Deploying the application without specifying it does not work. And it needs to be known at build time. That's the main issue.

IMPORTANT:

  • We are using React Router, so it must work properly when the application is deployed
  • We want to deploy a single build artifact (a single Docker Image) across all environments
5
  • Usually, it's the CI that will set/use different ENV variables and automatically set the right PUBLIC_URL before building. Commented Apr 20, 2021 at 13:23
  • 1
    Why do you specify PUBLIC_URL? It's not required to build and serve from nginx. Seems like your infrastructure is bleeding into your application. Commented Apr 20, 2021 at 13:35
  • ...but since you want to deploy a single build on the different envs, have you thought about just using a relative PUBLIC_URL? Commented Apr 20, 2021 at 13:41
  • @EmileBergeron Yes, we want to deploy a single build to different environments. We are using a relative PUBLIC_URL but the problem is that it is different for each environment. @im_baby Without it, the application is looking for static assets starting from the root "/", which is not the desired behavior. If the application is served under https://www.example.com/company/app-env01/react_application/ we need (for example) the CSS to be requested from /company/app-env01/react_application/static/css/. This is possible when PUBLIC_URL is set to /company/app-env01/react_application/ Commented Apr 20, 2021 at 14:33
  • 2
    What I mean is, use a relative URL that starts after company/app-env01/react_application, like a simple ./ and let your infrastructure do the routing. Commented Apr 20, 2021 at 14:50

1 Answer 1

5

Solution

@Emile got me thinking about using relative URLs again, along with configuring Nginx to handle it correctly. Thank you!

Adding this location block to the Nginx configuration solved the problem:

  location ~ .(static)/(js|css|media)/(.+)$ {
    root   /usr/share/nginx/html;
    try_files $uri $uri/ /$1/$2/$3;
  }

Here's the full configuration (simplified for the sake of clarity):

server {
  listen 80;
  
  # This rule internally redirects any relative requests for static files so that they start from the root
  # Example, it internally redirects `/RANDOM/SUBPATH/static/js/main.xxx.chunk.js` to `/static/js/main.xxx.chunk.js`
  # Effectively, it strips everything that comes before `/static...`
  location ~ .(static)/(js|css|media)/(.+)$ {
    root   /usr/share/nginx/html;
    try_files $uri $uri/ /$1/$2/$3;
  }

  location / {
    root   /usr/share/nginx/html;
    index  index.html;
    try_files $uri $uri/ /index.html;
  }
 
  error_page   500 502 503 504  /50x.html;
 
  location = /50x.html {
    root   /usr/share/nginx/html;
  }
}

Further explanation

The reason that using relative URLs originally did not work is:

This would work just fine:

/company/app-env01/react_application/static/js/xxxxxxxx.chunk.js

but this would not:

/company/app-env01/react_application/RANDOM/SUBPATH/static/js/xxxxxxxx.chunk.js

I needed a way to "strip off" the /RANDOM/SUBPATH part, whatever that part is, and I achieved it adding the Nginx location block listed above.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.