NextJS is overblown.
I know that's a controversial statement, but I have little interest in using React within an SSR project. Honestly, if I wanted to do that, I’d use something like Go + HTMX, or Laravel, or even ASP.NET! However, I’ve been working on a project built as a SPA because I like decoupled front and back ends.
The trick is figuring out a good way to run them simultaneously.
I’ve read articles that show you how to use a Vite plugin of sorts to run both simultaneously, and it does work. The issue here is that when you want to deploy the app, there isn't a good way to build and ship the code onto a server so it can be accessed by users. In my particular setup, I wanted to do this with Docker.
So here’s how I pulled this off using a Makefile.
Project setup and structure
Before I get into the configuration, here is what my directory looks like in VSCode.
The relevant directories and files are:
app
- The React app that is started with Vite.server
- The Express API.makefile
- The file that contains the script to run the project..env
- Stores environment variables.
Configuring Vite proxy
The first thing you’ll need to do if you want a similar setup is configure the Vite proxy.
In my app/vite.config.ts
, I started by using dotenv
to pull in the .env
file and set up the values as environment variables. Then I have server.proxy
configured to redirect all requests to the /api
path to localhost
plus whatever port I have defined in my environment variables. This is the same variable used by the Express app to start.
Here is the contents of that file.
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { config } from 'dotenv'
config()
export default defineConfig({
plugins: [react()],
server: {
proxy: {
'/api': {
target: `http://localhost:${process.env.SERVER_PORT}`,
},
}
}
})
So whenever I want to call my API, I can just use /api
and it will forward requests to the Express app like so:
const res = await fetch("/api/users/me")
Starting the dev environment
The contents of my makefile
is what makes this entire thing possible.
I essentially have separate scripts to run each project. run_server
will kick off the Express server process, and run_app
will kick off the Vite process. I’m also copying the .env
file from the root of the project into each of the project directories so dotenv
can do its thing.
Finally, by having a parent run
script that calls make with the -j
switch, I can pass in both scripts a the same time and run them in parallel.
run:
make -j 2 run_server run_app
run_server:
cp -f .env server/.env
cd server&&npm run dev
run_app:
cp -f .env app/.env
cd app&&npm run dev
Simply calling make run
from my terminal will kick off both projects!