Multi Tenant App in React - reactjs

I'm building a multi tenant app in React (with Webpack setup via base, dev and prod config files), and I'm wondering the best way to create and access per-tenant variables.
When I run my app with:
npm run start tenant1
I am able to access tenant1 in Webpack by using this:
const tenant1 = process.argv[process.argv.length -1];
However, now I'm wondering what is the best way to make that variable globally accessible. My hope is to use that variable to the create a folder structure within the app along the lines of:
/app/${tenant}/img/
/app/${tenant}/css/
/app/${tenant}/components/
Ideally without having to import a variable into every single javascript file.
Any suggestions or links to existing setups would be greatly appreciated.

Update Jan 2019:
I've found a way to achieve this with Create-react-app, not perfect but it works and achieves the following:
Run a React app on port 3000 that works for multiple domains simultaneously.
Forward all requests not handled by React to a back end.
Optionally use SSL in development.
Create-react-app has a proxy option that is very easy to setup. Simply add the following line to your package.json file:
"proxy": "http://localhost:5000"
However, this will not work for multiple domains. There is a more advanced proxy configuration available.
After following these steps, you will be able to control where different requests are sent, but it does not entirely provide the ability to proxy multiple domains - to achieve this:
Create the file .env in the root of your create-react-app project.
Add the following to it:
NODE_PATH=src/
DANGEROUSLY_DISABLE_HOST_CHECK=true
# optionally add this line for SSL in development
HTTPS=true
From the advanced proxy instructions above, you should end up with a file called setupProxy.js in the root of your /src folder - change to the following:
const proxy = require('http-proxy-middleware')
const options = { target: 'https://[::1]:8000', secure: false }
module.exports = function(app) {
app.use(proxy('/api', options))
app.use(proxy('/graphql', options))
}
The magic part is the https://[::1]: 8000, which will forward all domains from the root request to the same back end. This doesn't seem to be well documented anywhere, but I believe it is the IPv6 equivalent of 127.0.0.1.
After this, you can add entries to your hosts file (for example: 127.0.0.1 some-domain.example.com), and in your React app use just the paths (/api or /graphql) and requests should be proxied to the same domain as the browser is running on.
Original answer:
I ended up taking a fairly manual approach to this.
I'm using a react/redux boilerplate, but I've modified the npm scripts like so:
"start:tenant1": "cp -r ./tenants/tenant1 ./app/tenant && cross-env NODE_ENV=development node server",
"start:tenant2": "cp -r ./tenants/tenant2 ./app/tenant && cross-env NODE_ENV=development node server",
Each tenant folder is copied to the app when the development server is run with the relevant command, and files are named the same within each tenant folder (masthead.svg, vars.js, etc) so that imports throughout the app can be static.
Mostly this works because I'm not using a node server in production, a static build folder is generated by the boilerplate.
Hope this helps someone.

Related

Base URL Discrepancy Between Localhost and Deployed - create-react-app

I am building a web application from the ASP.Net Core React template, whose "ClientApp" React portion appears to be a 'create-react-app' project. When I deploy the app to IIS, it gets hosted in a subfolder from the root, i.e. http://myserver.com/SubApp. Because of this, I have changed the "homepage" value in package.json to http://myserver.com/SubApp.
What I am now experiencing is that when I am making fetch calls in my javascript code locally, if I use fetch('/myendpoint'), the the url requested locally is https://localhost:44315/myendpoint (which works), but when deployed, this url becomes http://myserver.com/myendpoint, which does not work.
Conversely, when I make the endpont fetch('myendpoint') (no leading slash), the server requests the correct URL http://myserver.com/SubApp/myendpoint but localhost fetches the incorrect URL, https://localhost:44315/SubApp/myendpoint.
I understand that the leading slash makes the request from the root, but the root appears to be different in localhost vs. on the server.
For local debugging purposes, I tried setting a proxy in package.json to https://localhost:44315 so that hopefully fetch('myendpoint') (no leading slash) would work in my local environment, but when I try this, chrome prompts me to sign in repeatedly without ever successfully making the request.
I feel like I am almost there, but I must be missing something. How can I configure my package.json (or other project configuration) to make the fetch commands succeed on both localhost and my server, without doing something hacky like checking the URL in the javascript code before every fetch?
What you need is Proxying API Requests in Development.
It easily allows you to call any endpoint in development and forward it to your backend or other location without CORS issues or your problem of mismatched endpoints.
You will have to use the manual configuration option, since the default proxy setup only helps with host/port forwarding.
Install http-proxy-middleware as a dev dependency:
$ npm -i http-proxy-middleware --save-dev
Following the guide linked above, you can use this configuration:
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function(app) {
app.use(
'/SubApp/*',
createProxyMiddleware({
target: 'localhost://44315', // or anything else, point to your dev backend
pathRewrite: {
'^/SubApp': '/', // remove base path
},
})
);
};
You can then fetch with the /SubApp part in the request which will be removed in developement. You can use other configurations to achieve this, this is one example.
I had this same experience as well and found that setting the homepage value in the package.json is a dead end. What you want to do is make use of the PUBLIC_URL environment variable to set a full absolute url in your .env . There may be a way to use relative urls, but there were some edge cases that just made it cleaner to use an absolute URL.
When you set this environment variable you want to make use of it in the following places:
Your Routes (if you use react router)
The path should be prefixed by process.env.PUBLIC_URL
Your internal links should also be prefixed by the env var.

Webpack: is that possible to hide source map only in production?

I want to debug my react app locally in the browser,
but i set devtool property to hidden-source-map in webpack.config.js file in order to hide my source code in production.
Is there any possibility to debug locally without exposing my source map to production?
You can enable source-map on production, but host it in a server that only allows request from whitelisted IPs. If a request came from a non-whitelisted IP, you can just return error 403 or 404.
Let's say you are uploading your source maps to amazon s3:
// we use webpack.SourceMapDevToolPlugin for more flexible setups. Set the 'devtool' option to 'false' when you are doing this.
plugins: [
new webpack.SourceMapDevToolPlugin({
filename: '[name].[contenthash].js.map',
// this is a s3 private bucket that is only accessible via whitelisted IPs
// regular user will not be able to access the bucket
append: `\n//# sourceMappingURL=https://s3.ap-southeast-1.amazonaws.com/sources-maps/[url]`,
...options,
}),
// other plugins.
]
Your generated JS will still have the magic comment at the end of the file:
//# sourceMappingURL=https://s3.ap-southeast-1.amazonaws.com/sources-maps/main.js.map
But browser devtools can only download the source map if it is accessing it from a whitelisted IP, such as your office network, a company VPN, etc.
Another approach is you can just set the sourceMappingURL to localhost. With this approach, you should have all the *.map files available locally on your machine. When you want to debug production code, simply start a static server (e. g.: ecstatic) locally to serve the source maps. This way, you can be sure only you can access the source maps. But, it requires manual work to download and serve the source maps locally.
You can use the environment variable for this. It would be something like this:
devtool: process.env.SOURCE_MAP ? 'inline-source-map' : 'hidden-source-map',
Then you could run tests like this for example:
SOURCE_MAP=true yarn test (just an example)
You should checkout this module https://www.npmjs.com/package/dotenv. It's possible to create .env files and use the configuration for different flows, like test and build, so you don't have to specify env variables manually, before running the command.
If you don't want to use the environment variables, you could create separate webpack config files, maybe one that has all the common stuff between production and test environments and then more specific configs that extend the common config (one for test env with source maps enabled and one for production with source maps disabled).

React SPA dynamic environment configuration

I'm building React SPA application from ground up using create-react-app and setting up uri address for API server of my SPA. According to official documentation suggested way is to create environment .env files for such kind of needs. I'm using a continuous delivery as part of development workflow. After deployment React SPA application goes in one Docker container and API goes to another. Those containers are deployed in separate servers and I do not know exactly what uri for API will be, so there is no way to create separate .env file for each deployment. Is there any "right way" to provide dynamic configuration for my SPA application so I can easily change environment parameters
API URI examples in SPA
// api.config.js
export const uriToApi1 = process.env.REACT_APP_API1_URI;
export const uriToApi2 = process.env.REACT_APP_API2_URI;
// in App.js
import { uriToApi1, uriToApi2 } from '../components/config/api.config.js';
/* More code */
<DataForm apiDataUri={`${uriToApi1}/BasicService/GetData`} />
/* More code */
<DataForm apiDataUri={`${uriToApi2}/ComplexService/UpdateData`} />
Let's imagine that you build your frontend code in some dist folder that will be packed by Docker in the image. You need to create config folder in your project that also will be added in dist folder (and obvious, will be packed in Docker image). In this folder, you will store some config files with some server-specific data. And you need to load these files when your react application starts.
The flow will be like that:
User opens your app.
Your App shows some loader and fetches config file (e.g. ./config/api-config.json)
Then your app reads this config and continues its work.
You need to setup Docker Volumes in your Docker config file and connect config folder in Docker container with some config folder on your server. Then you will be able to substitute config files in a docker container by files on your server. This will help you to override config on each server.

Cannot GET index.html Azure Linux Web App

We created a Linux Web App in Microsoft Azure. The application is static written with React (html and Javascript).
We copied the code into the wwwroot folder, but the application only showing only hostingstart.html and when we try to get page index.html we have this error:
Cannot GET /index.html
We tried with a sample of Azure in GitHub (https://github.com/Azure-Samples/html-docs-hello-world) but the error is the same.
The url is this: https://consoleadmin.azurewebsites.net/index.html
Last week the application was running correctly.
We forget to do something?
MAY 2020 - You don't have to add any javascript files or config files anywhere. Let me explain.
I was facing this exact same issue and wasted 6 hours trying everything including the most popular answer to this question. While the accepted answer is a nice workaround (but requires more work than just adding the index.js file), there's something a simpler than that.
You see, when you just deploy an Azure Web App (or App Service as it is also called), two things happen:
The web app by default points to opt/startup/hostingstart.html
It also puts a hostingstart.html in home/site/wwwroot
When you deploy your code, it replaces hostingstart.html in home/site/wwwroot but the app is still pointing to opt/startup/hostingstart.html. If you want to verify this, try deleting opt/startup/hostingstart.html file and your web app will throw a "CANNOT GET/" error.
So how to change the default pointer? It's simpler than it looks:
Go to Configuration tab on your web app and add the following code to startup script:
pm2 serve /home/site/wwwroot --no-daemon
If this web app is a client-side single-page-app and you're having issues with routing, then add --spa to the above command as follows:
pm2 serve /home/site/wwwroot --no-daemon --spa
This will tell the web app to serve wwwroot folder. And that's it.
Image for reference:
Screenshot explaination
PS: If you only set the startup script without deploying your code, it will still show the hostingstart.html because by default that file lies in the wwwroot folder.
Ok you are gonna love this. This happened to me today also. Same exact thing.
I am pretty sure the azure team flipped a switch somewhere and we fell through a crack.
I found this obscure answer with no votes and it did the trick (with a little extra finagling)
BONUS! this also fixed my router issues I was having only on the deployed site (not local):
Credit: #stormwild: Default documents not serving on node web app hosted on Azure
From #stormwild's post see here:
https://blogs.msdn.microsoft.com/waws/2017/09/08/things-you-should-know-web-apps-and-linux/#NodeHome
Steps:
Go to your azure portal, select your app service and launch ssh
In ssh terminal, navigate via command line to /home/site/wwwroot
create index.js there with the following code:
var express = require('express');
var server = express();
var options = {
index: 'index.html'
};
server.use('/', express.static('/home/site/wwwroot', options));
server.listen(process.env.PORT);
NOTE: Be sure to run npm install --save express also in this folder else your app service will crash on startup
Be sure to restart your app service if it doesn't do so automagically
A workaround, I changed the webapp stack to PHP 7
Another solution would be to add a file called ecoysystem.config.js right next to your index.html file.
module.exports = {
apps: [
{
script: "npx serve -s"
}
]
};
This will tell pm2 to associate all requests to index.html as your app service starts up.
Very helpful information here: https://burkeholland.github.io/posts/static-site-azure/

How to inject port and host using ENV variable in Create React App Proxy settings?

Is it possible to set proxy settings in CRA differently than with package.json? For example using ENV variables?
From CRA docs it states
Use environment variables to inject the right server host and port into your app.
Is this sentence about proxy or dev server itself?
According to the this and this, the only way to influence proxy settings is via package.json.
Note: this feature is available with react-scripts#2.0.0 and higher.
You can manually configure proxy now. You need to install http-proxy-middleware first.
npm install http-proxy-middleware --save
Then create a file src/setupProxy.js with following content.
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function(app) {
app.use(
'/api', // You can pass in an array too eg. ['/api', '/another/path']
createProxyMiddleware({
target: process.env.REACT_APP_PROXY_HOST,
changeOrigin: true,
})
);
};
You can add proxy now in the .env file, remember you need the REACT_APP_ prefix for development server to pick up the environment variable.
REACT_APP_PROXY_HOST=http://localhost:5000
Ref: Proxy manually
Regarding:
Use environment variables to inject the right server host and port
into your app.
This refers to neither the proxy server nor the dev server. This is a recommendation on how to handle per-environment server settings if proxy does not provide enough flexibility for you (i.e. if you need to use protocols other than http, https, or ws). For example, if you have an api call within your code, you can use an environment variable to do something like this:
axios(process.env.REACT_APP_BASE_URL + endpoint, options).then(data=>{ console.dir(data); })
Where each environment has it's own definition of REACT_APP_BASE_URL.
Without package.json:
It is not possible to influence proxy settings without modifying package.json.
See Configuring the Proxy Manually (CRA docs)
package.json is the only documented way to influence proxy settings.
You can also see line 96 of CRA's start.js. This implementation leaves absolutely no ability to use ENV vars to inject the host/port to CRA's proxy settings.
This is really one of the reasons I detest CRA, to be honest. All of this behavior is so very easily handled when not bound by the limitations of CRA.
You could just eject create react app:
npm run eject
All of these problems will go away instantly... The start.js script referenced above is a bit of the code that is ejected. So you would very easily be able to replace those proxy settings with ENV vars.
Just be certain to create a checkpoint for yourself prior to doing this as there is no reverting an ejection.
However--if you must stick with CRA--you CAN very easily modify package.json using environment variables.
How to inject ENV vars into package.json at build time:
This is essentially what CRA does with the REACT_APP_ env vars (though it directly injects them into process.env).
package.json is just a big JSON object, so all you need to do is parse it on the server side right before you deploy, update the "proxy" value to the ones provided by your ENV vars, save the file, and then run your build/deploy.
Here is a very simple example of how to achieve this, you just need to run this script before deployment in a context where you still have access to process.env:
const fs = require('fs');
// read/process package.json
const file = './package.json';
let pkg = JSON.parse(fs.readFileSync(file).toString());
// at this point you should have access to your ENV vars
pkg.proxy = `${process.env.HOST}:${process.env.PORT}`;
// the 2 enables pretty-printing and defines the number of spaces to use
fs.writeFileSync(pkg, JSON.stringify(file, null, 2));
I know this doesn't explicitly answer the question of how to handle this "without package.json", but I don't think that's the necessary approach anyways.
Further, this DOES asnswer the title question of "How to inject port and host using ENV variable in Create React App Proxy settings?"
Is it possible to set proxy settings in CRA differently than with package.json?
No. It is not possible. Well, at least not yet (2017/01/14).
The React proxy functionality is getting a bit complicated and as a result, the idea of setting up a proxy in a React app from env variables will be implemented as soon the simplification of the "advanced" proxy configuration is finished.
Is this sentence about proxy or dev server itself?
It Is about the dev server itself. All proxy configuration, as things stand, is configured in package file.
You can define env variable for port and host.
REACT_APP_PORT: "PORT of Application",
REACT_APP_HOST: "host of application"
To inject all the env to your application you can use webpack define plugin.
You need to add in plugins section like.
const env = require("path to your env file")
//other webpack settings
plugins: {
//plugins
new webpack.DefinePlugin(env)
}
Now if you see the code of create-react-app it looks in env variable first to find the port and host.
Also, note that
You MUST create custom environment variables beginning with REACT_APP_. Any other variables except NODE_ENV will be ignored to avoid accidentally exposing a private key on the machine that could have the same name

Resources