I recently launched a Heroku pipeline with staging and production environments. My app is built with React, NodeJS, and Webpack.
When I deploy to staging, my baseURL is the production url, but it should be the same as the frontend staging url.
I've been able to isolate the problem to the Webpack mode specifically. I have no way to control the baseURL for staging because the Webpack mode and Heroku seem to require either development or production. Setting NODE_ENV=staging doesn't seem to change anything.
ERRORS:
I am seeing this error in my staging console:
xhr.js:258 Refused to connect to 'http://localhost:4000/api/users/login' because it violates the following Content Security Policy directive: "connect-src 'self' https://stagingapp.herokuapp.com".
And this one in production:
Access to XMLHttpRequest at 'https://stagingapp.herokuapp.com/api/users/login' from origin 'https://prodapp.herokuapp.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The 'Access-Control-Allow-Origin' header has a value 'https://stagingapp.herokuapp.com' that is not equal to the supplied origin.
This makes it looks like a CORS and CSP issue, because it kind of is. But it's only a problem because my Webpack is disrupting the baseURL. When those align, everything works as expected.
Here is what I have done:
In Heroku, I added config vars (environment variables) -- NODE_ENV=staging for staging, and NODE_ENV=production for production.
I have tried various ways of adjusting my Webpack mode
I've tried changing my build script around but Heroku only accepts build or heroku-postbuild, etc. I can't seem to use staging-specific scripts
I have tried controlling the baseURL with Axios, but it appears that my Axios config isn't doing much at all because the baseUrl is being determined in my build and Axios works at runtime. If I could get my Axios to handle the URLs I think that would solve the problem, but Webpack keeps getting in the way.
I've also changed my staging environment to production to see if that would help. Now my staging environment works but my prod environment is having the same problem where it is calling my staging URL as the base URL instead of the prod url.
I've also read over all of the documentation multiple times but there isn't much on how to handle a Webpack build on Heroku.
Here are the relevant parts of my code:
WEBPACK CONFIG:
mode: process.env.NODE_ENV === 'production' ? 'production' : 'development'
PACKAGE.JSON BUILD SCRIPT: This has been a million things, and I have tried it with NODE_ENV=staging and without the --mode production tag.
"build": "webpack --mode production"
AXIOS CONFIG - This doesn't seem to be doing anything in production or staging
import axios from 'axios';
// Define base URLs for different environments
const baseURLs = {
development: 'LOCAL HOST URL',
staging: 'STAGING URL',
production: 'PROD URL',
};
// Determine the environment based on NODE_ENV
const environment = process.env.NODE_ENV;
// Create an instance of Axios with a custom configuration
const instance = axios.create({
// Set the base URL for requests
baseURL: baseURLs[environment],
withCredentials: true,
});
export default instance;
After slipping and bumping my head while standing on my toilet to hang a clock, I have discovered the answer. As usual, it is quite simple. Here's what I did:
Create a NODE_ENV
config var in both staging and production and set it to 'production'
Create a TARGET_ENV
config var and set it to 'staging' in staging and (you guessed it) 'production' in production. Make sure to also set it to 'development' in your .env file.
Set your Webpack mode in your config file to the following:
mode: process.NODE_ENV === 'production' ? 'production' : 'development',
plugins: [
new webpack.DefinePlugin({
TARGET_ENV: JSON.stringify(process.env.TARGET_ENV),
}),
],
};
This will create a global variable at build time using your config var.
Keep your build script simple in your package.json: "build": "webpack",
In your axiosConfig.js (or wherever you're storing your baseUrl), use TARGET_ENV
without process.env
attached. (Also, I tried process.env.REACT_APP_BLAH_BLAH
and that didn't work, fyi.)
import axios from 'axios';
let baseURL;
// Set environment to development based on TARGET_ENV
if (TARGET_ENV === 'production') {
baseURL = 'https://prod.herokuapp.com';
} else if (TARGET_ENV === 'staging') {
baseURL = 'https://staging.herokuapp.com';
} else {
baseURL = 'http://localhost:4000';
}
// Create an instance of Axios with baseURL and withCredentials: true
const instance = axios.create({
// Set the base URL for requests
baseURL,
withCredentials: true,
});
export default instance;
In your pipeline, set your staging environment to autodeploy when you push to your staging branch, and your production branch to autodeploy when you push to main.
Push directly to each branch separately, either from your console or from a Github PR. This will create a separate build for each environment. It worked for me, I hope it works for you. Godspeed.
Oh, and I do still have one outstanding question:
I am not sure if, after running the initial production build, the 'promote to production' button in the Heroku dashboard will work.
Will it ONLY push the recent changes from staging, or will it overwrite my production build? I'll test it out eventually, but if anyone knows, feel free to drop a comment.