NextJS is overblown.

I know thats 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 kinda run both simultaneously, and it does work. The issue there is that when you want to deploy the app, there isnt 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 it’s 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 make will 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!