React build once and deploy anywhere configurable URL - reactjs

For my react project I'm trying to implement a global configuration that can pickup deploy URLs or any other config variables based on different environments like development, staging, or production. Optionally, the base URL path can be fetched dynamically from a co-deployed file, such as const config = fetch('/config.json'). Precisely,
All invocations of the API obtain the base URL path from that one configuration location, no other hardcoded URL base paths should exist in the code.
The npm build process should start with npm run-script build ${ENV_BASED_API_URL}.
In short, I need a proper "build once, deploy anywhere" (aka no divergence between development and production) but also the flexibility to change which API the frontend talks to.
For the same, I tried some approach based on following references:
https://www.cotyhamilton.com/build-once-deploy-anywhere-for-react-applications/
https://hasniarif.io/handle-runtime-environment-in-react/ and some other SO references.
So, I created a config.js file in the /public folder of my react-app, whose content goes like this:
const rootUrl = `${location.protocol}//${location.host}`;
const envs = {
// Production
'http://localhost:6000': {
APP_ENV: 'production',
API_URL: 'https://api.example.io',
...some more other keys
},
// Staging
'http://localhost:5000': {
APP_ENV: 'staging',
API_URL: 'https://staging-api.example.com',
...some more other keys
},
// Development
'http://localhost:3000': {
APP_ENV: 'development',
API_URL: 'http://localhost:1080',
...some more other keys
},
};
if (envs[rootUrl]) {
// Set environment variables based on the URL the app is being accessed from
window.env = envs[rootUrl];
} else {
// Redirect to production if the rootUrl is unknown
window.location.replace(Object.keys(envs)[0]);
}
And the package.json is as:
"scripts": {
"start": "react-scripts start",
"postbuild": "react-scripts ",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
Now, I am unable to figure out how to implement the correct way to connect the config.js file with the package.json to create build dynamically & then deploy as per different APP_ENV and also handle configs from any part of the application.
If anyone please guide me how can I achieve this ?

Related

Launch mock server on vercel

I have a React project and installed mock server with json-server node module.
(For your note: Here is the json-server documentation for better understanding. [link]1)
I can start mock server with npm script [npm run mock].
After run the script, mock server start working with this link: localhost:3000.
This react project is working well on local environment.
Now I am going to deploy react project on vercel.
But I have no idea for start mock server on vercel.
How can I start mock server on vercel?
Here is the scripts of package.json file for the application.
{
...
"scripts": {
"start": "PORT=3001 react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"mock": "json-server --watch src/mock/db.json",
"preinstall": "npm install --package-lock-only --ignore-scripts && npx npm-force-resolutions"
}
...
}
Please ask me more information what you want to check.
I had a similar issue and this article save my life.
https://shadowsmith.com/how-to-deploy-an-express-api-to-vercel
Instead of deploying together with my React project, I deploy JSON Server as a separate mock server.
First of all, you need to create a server.js to run JSON Server as a module.
Reference: https://github.com/typicode/json-server#module
At the end of the file, export server in order for Vercel to turn Express into a serverless function
// Export the Express API
module.exports = server;
Last but not least, create a vercel.json in order to deploy to Vercel.
Here are my scripts for your reference purpose: https://github.com/kitloong/json-server-vercel

How to run a different build command for staging environment when publishing a react amplify application

I have an amplify react application with two environments so far: prod and staging.
Then I have my .env.staging and .env.production files with different values for an API URL.
Therefore, in my package.json I have the following scripts ready for the deployment:
"build": "react-scripts build",
"build:staging": "env-cmd -f .env.staging react-scripts build",
Now the problem comes as I don't know how to make amplify publish command to run one or the other depending on the environment.
No matter which amplify env checkout I choose, the configuration used on the 'publish' command is shared in the 'project-config.json', and it looks like the following:
{
"projectName": "whatever",
"version": "3.0",
"frontend": "javascript",
"javascript": {
"framework": "react",
"config": {
"SourceDir": "src",
"DistributionDir": "build",
"BuildCommand": "npm.cmd run-script build",
"StartCommand": "npm.cmd run-script start"
}
},
"providers": [
"awscloudformation"
]
}
Is there any way to achieve what I'm looking for?
Thanks for your help in advance.
I understand this question was asked almost 1 year ago now, but i encountered this same problem this morning and would like to offer my solution to the problem.
Currently (asof 04/03/22) there is still no official solution to the problem and as such, you will be required to edit your build script to build your content based on the environment dynamically.
The way we have currently implemented this is by creating a build JS script in our root (named build-env.script.js) containing the following code:
#! /usr/bin/env node
(function() {
// bring in child_process to use spawn command
const cp = require('child_process');
// bring in the amplify local-env-info.json to see current environment
const buildInfo = require('./amplify/.config/local-env-info.json');
// spawn the build command based on the env name:
// npm run build-production on prod or npm run build-staging on staging
const cmd = cp.spawn(`npm run build-${buildInfo.envName}`, { shell: true });
// echo output of the commands to the console
cmd.on('spawn', () => console.log('Running build command for:', buildInfo.envName));
cmd.stdout.on('data', (d) => console.log(d.toString()));
cmd.stderr.on('data', (d) => console.log(d.toString()));
cmd.on('exit', () => console.log('Build Completed'));
})();
As a sidenote, the JSON file in question is created by amplify and therefore can be determined as a source of truth when it comes to looking for the current environment name, my file for example looks like this:
{
"projectPath": "path/to/project/frontent",
"defaultEditor": "vscode",
"envName": "production"
}
Whilst my package.json looks like this:
{
...
"scripts": {
...
"build": "node ./build-env.script.js",
"build-staging": "ng build --configuration staging",
"build-production": "ng build --configuration production"
}
}
However you will need to modify the env names and scripts accordingly (as our project is an angular project.

React app unable to access root's env file

Context:
- MERN stack app
- Deployed to Heroku
- React app created with create-react-app
Here is my folder structure:
folder structure
In my server app.js, I can access the .env variables by requiring it like this: require('dotenv').config({ path: '../.env' });
I am trying to access the env variables on my React app, but I cannot seem to access the variables in the root .env file, and I cannot seem to specify the path in React (can I?) for the .env like in server/app.js.
The only way I can get it to read any .env variable value is if I move the .env file to the client directory, so my workaround for now is to move .env to my client directory and update the code in my server to require it as such: require('dotenv').config({ path: '../client/.env' });
This workaround fixes it, but is there a better way/best practice to allow for the .env file be in the root folder?
Do I need to eject create-react-app and set up some custom configurations?
More info:
My local .env file contains:
REACT_APP_API_URL=http://localhost:5000
My client/package.json scripts:
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
Sorry, miss understood your question.
In create-react-app you don't have to require the dotenv module, ref: https://create-react-app.dev/docs/adding-custom-environment-variables/
Are you sure your local environmentis development?

React and Nodejs

I've issue in connecting react and express nodejs. On production they are fine together but on dev they are not connected. I've read a lot of articles and none of them worked with me. I'm deploying on heroku. And on production how can I replace local host end points with heroku url.
const express = require("express");
const path = require("path");
const app = express();
const publicPath = path.join(__dirname, "", "../build");
app.use(express.static(publicPath));
app.get("*", (req, res) => {
res.sendFile(path.join(publicPath, "index.html"));
});
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log("Server is up on port " + port);
});
for package.json/
"scripts": {
"start": "node server/server.js",
"dev": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
}
Assuming your reactjs and node applications are deployed seperately, you can follow these steps:
For backend dotenv package is very popular, and easy to setup.
1-) add .env file to the root folder with your environment variables like this:
MONGO_URL=mongodb://localhost:27017/
Please note that there should be no empty space.
2-) In your main file, import dotenv as soon as possible before reading any variables.
require("dotenv").config();
3-) Now you can accesss your variables like this:
const dbUrl = process.env.MONGO_URL;
4-) put your .env file to the .gitignore file
5-) Specify the version of node.js by adding an engines section in package.json that will be used to run your application on Heroku.
For example:
"engines": {
"node": "10.1"
}
6-) After you deploy your node application to the heroku, add your variables using PROD values as described here in heroku docs.
If you created your reactjs app using create-react-app you can follow these steps:
1-) Add .env file to the root folder. Your variable names must start with REACT_APP_...
REACT_APP_API_URL=http://localhost:3001/api
2-) Access your variables like this in your code:
process.env.REACT_APP_API_URL
3-) put your .env file to the .gitignore file
4-) After you deploy your reactjs app to the heroku, set prod variables in Config Variables section, as we did for node app.

Not able to fetch .env urls to my component on my react project

I am using 3 .env file like .env.prod, .env.dev and .env. But not able to fetch the url to my component.
I am using react 16.9.
Can you please help me why I am not able to fetch it?
in my .env / .env.dev files
loginUrl = = "http://localhost:8080/api/2.0/admin/auth/login"
in my package.json files
"scripts": {
"start": "cp ./.env.dev .env && react-scripts start",
"build:dev": "cp ./.env.dev .env && react-scripts build",
"build:stage": "cp ./.env.stage .env && react-scripts build",
"build:prod": "cp ./.env.prod .env && react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},```
Inside my component, when I am printing it it is giving undefined.
console.log(process.env.loginUrl) is giving undefined
Using react-script
Looking at react script adding-custom-environment-variables documentation, variables are automatically loaded from .env if:
Note: 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. Changing any environment variables will require you to restart the development server if it is running.
Seams like the problem come from the name of your variable, try renaming in REACT_APP_LOGIN_URL
Note: this feature is available with react-scripts#0.5.0 and higher.
If using Webpack instead of react-script
You need to use webpack DefinePlugin to inject environments variables in your code.
In your webpack.config.js file :
require("dotenv").config(); // will load .env file in process.env
const webpack = require("webpack");
...
plugins: [
...
new webpack.DefinePlugin({
"process.env": {
NODE_ENV: JSON.stringify("production"),
SENTRY_DSN: JSON.stringify(process.env.SENTRY_DSN),
BUILD_DATE: JSON.stringify(new Date()),
TRAVIS_COMMIT: JSON.stringify(process.env.TRAVIS_COMMIT)
}
}),
...
]
Make sure you have strings only as if the value isn't a string, it will be stringified (including functions).
Then in your code, you can call console.log(process.env.NODE_ENV)

Resources