What's the simplest way to "build" a static web page and Node backend such that the Node server runs in HTTPS in dev mode (but not production) and the static web page can point to https://localhost/foo
in dev, but just /foo
in production?
Long Story
This is probably a mess, and probably not The Right Way to do things, but this is my first time using React for anything other than an SPA, so I've never had to deal with Webpack/Babel (or any other build tools) before.
I have a static landing page, which has a couple of React components in it (i.e., it's not a SPA where it's all React, it's just loading React components to a few root
s). One of them is for handling Stripe payments. There's also a simple back-end Node server to handle the Stripe payment-intent creation and web hooks that get registered with Stripe.
To simplify development, I've put this all in one repository, that looks like this:
repo/app.js
devserver.js
index.html
package.json
client/package.json
webpack.config.js
src/<react stuff>
server/package.json
/index.js
/<other node stuff>
public/<webpack output>
The index.html
is the static page. The top-level app.js
, devserver.js
, and package.json
really only exist for development, so I can load the static web page on localhost and watch for source changes on both the React and Node sides.
repo/package.json
:
{
"name": "landing-page",
"version": "0.99.2",
"description": "Static HTML for landing page",
"private": true,
"scripts": {
"start": "npm-run-all build:dev:client -p start:* watch:client",
"start:devserver": "nodemon devserver.js",
"start:server": "cd server && npm start",
"watch:client": "cd client && npm start",
"build:dev:client": "cd client && npm run build:dev",
...
So, if I npm start
, it
- builds the React stuff —
repo/client/package.json
:
{
"name": "landing-page-react",
"version": "0.99.2",
"description": "React components for landing page",
"private": true,
"main": "index.js",
"homepage": ".",
"scripts": {
"build:dev": "webpack --config ./webpack.config.js --mode development",
"start": "webpack --watch --config ./webpack.config.js --mode development",
...
and repo/client/webpack.config.js
:
const path = require('path');
const Dotenv = require('dotenv-webpack');
module.exports = (env, argv) => {
console.log('Webpack mode: ', argv.mode);
const config = {
entry: path.resolve(__dirname, './src/index.js'),
plugins: [
new Dotenv()
],
module: {
rules: [
{
test: /\.(js)$/,
exclude: /node_modules/,
use: ['babel-loader']
}
]
},
resolve: {
extensions: ['*', '.js', '.jsx']
},
output: {
path: path.resolve(__dirname, '../public'),
filename: 'bundle.js',
},
devServer: {
contentBase: path.resolve(__dirname, '../public'),
},
mode: argv.mode
}
return config;
};
putting the output back up in repo/public
whence it's included in index.html
.
- Launches the Node server —
repo/server/package.json
:
{
"name": "landing-page-node-module",
"version": "0.99.2",
"description": "Process stripe payments for landing page",
"private": true,
"main": "index.js",
"type": "module",
"scripts": {
"start": "NODE_OPTIONS='--experimental-specifier-resolution=node --trace-warnings' nodemon index.js",
and the "dev" server, so I can actually see/use the index.html
page:
repo/devserver.js
:
const http = require('http');
const PORT = process.env.PORT || 3000;
const appServer = require('./app');
const httpServer = http.createServer(appServer);
httpServer.listen(PORT);
console.log(`Listening on port ${PORT}...`);
repo/app.js
:
const path = require('path');
const express = require('express');
const app = express();
app.use(express.static(path.join(__dirname)));
app.use('/', express.static('index.html'));
module.exports = app;
This all already seems a little preposterously complex, but, it works. If I navigate to localhost:3000
it loads my index.html
, which pulls in the React transpiled scripts from repo/public
; that frontend communicates fine with the Node server running on localhost:9292
; and if I make anyone changes to either the server side or the client side, it all gets "watched" and reloaded. So, so far so good.
Now the problem. I want to add logging both in the static web page and the React components, such that those logs go to the Node server.
In the index.html
I've added
<script>
function sendToLog(data) {
const body = JSON.stringify(data);
// Use `navigator.sendBeacon()` if available, falling back to `fetch()`.
(navigator.sendBeacon && navigator.sendBeacon('/api/log', body)) ||
fetch('/api/log', {body, method: 'POST', kee palive: true});
}
</script>
There are two problems with this. First, '/api/log'
would work fine in production, but doesn't in dev, because it needs to go to a different port. So I need to "build" the static index.html
such that it's '/api/log'
for production, but 'localhost:9292/api/log'
in dev. Second, I need the node server to run over https
in dev, because sendBeacon
requires that. Third, I'm hoping the React code will be able to rely on that sendToLog
function because it's available in the parent page, but am prepared for the possibility that I may need to do some custom building of the React components as well.
In production, this is all behind a Apache, which is in turn behind a CDN. The CDN forces https
and when the requests hit Apache, it just proxies any /api
URLs to the node server on localhost over http
, so ... everything Just Works. But there is no https
(nor Apache) in my dev environment.
I started down a path of putting a bunch of if ('production' !== process.env.NODE_ENV
code in the server to use a local CA and certs, but it occurs to me that this is not the right thing to do. It feels like the server should be built using the secure config when in dev, and not when in prod (because it's secured through other means, and why add the overhead). And, ideally, I'd like to use the same tool to build the static html file.
Given where I am, what's the simplest way to do this? Is this something that can reasonably/easily be done with Webpack and babel? Or do I need to add something else (I have no idea what, Gulp?) to my build process? And either way, any pointers on where to start configuring this?