Author avatar
Barista Posted on March 9, 2025

Serving static websites with Garcom

Nowadays, the new (maybe not so new) standard for React apps is using Next, but not so long ago, SPAs with Create React App or Vite were the most common type React projects. It would build into bundled Javascript and had to be served correctly in order to work as the SPA it was designed to be.

Some history

I remember writing a Go server that would act as reverse proxy to serve the React project for one of my previous employers. In that case, that server would do a lot more than just serving the React app files, for example, return proper 404 pages (with 404 status code and all) for invalid requests.

This was a required by one of their clients because of a security analysis report that would complain about stuff like "WordPress Admin Login Page Is Public", even though we didn't even use WordPress.

I guess their analysis was based on a stupid script that would just check the status code of some known routes, and since in SPAs the routing is done client-side, the status code would always be 200 - OK.

Looking back, I wouldn't say that the solution was great, it was written and deployed in a rush to comply with the client demands. That being said, since the solution provided a zero-config way for dockerizing React Apps, the project started to be used by most of the React projects of the employer (most of the times, with the "404 feature" disabled). Just the existence of a template Dockerfile that was simple and did a great job was enough to make the company adopt it. Anyway, I don't work there anymore, but wouldn't be surprised if it is still being used.

Svelte, but the same problems

I would not call myself a Frontend developer, I can do websites but I am not nearly as skilled as the frontend devs I've met, but in one of my attempts to learn a new tech, I picked Svelte. The project I had in mind for it, was simple and could've been done without any major frontend framework, but I thought it would be cool to use it to test Svelte SSG.

The project went ok, then I had to deploy it. I wrote a Dockerfile using Nginx as the server and deployed it in somewhere and called it a day. But then, I did another project and I had to deploy a Svelte SPA. I couldn't use the same Dockerfile, since that would require some extra Nginx config, and for a second I missed the good ol' Dockerfile we used in my previous employer.

With that memory, I also remembered about how messy the code was, so I thought to myself "I can probably do a simple server like that in very little time without all the crazy requirements, but the same core functionality". So I tried to write something in Go, and after some refinement I created Garcom. It doesn't have the "404 status code feature", but it can serve SPAs and SSGs!

Introducing: Garcom

The idea was to create a simple server that would be able to serve static websites and SPA, so I could use it to serve the abominations sites I wrote. That also became useful for one of my friends, that's already 2 users (2 more than most of my personal projects)! This nice blog you are reading, matter of fact, is served to you by Garcom!

The name comes from "garçom", that means "waiter"/"server" in Portuguese. It was written in Go, using Go HTTP server and chi. It works as a CLI app which is already dockerized so it's simple to use it in docker as well.

Installing and using

The project is open source, and available here. Since it's a Go project, you can install using Go command:

go install code.db.cafe/pauloo27/garcom/cmd/garcom@latest

You can see all the flags using garcom --help:

Usage of garcom:
  -catch-all string
        Path to a catch-all page (useful for SPAs)
  -clean-urls
        If enabled, URLs without extensions will be treated as '.html' (e.g., /page → /page.html)
  -error-404 string
        Path to the custom 404 error page
  -immutable-dir string
        Path to a directory with immutable files (sets aggressive caching headers)
  -port int
        Port to listen on (default 8080)
  -root string
        Path to the root directory to serve files from (default ".")

You can also use it in a Dockerfile, like this one from a suspicious blog:

# build...
FROM node:20 AS builder

WORKDIR /app

RUN npm install -g pnpm@latest-10
COPY svelte.config.js package.json pnpm-lock.yaml .npmrc tsconfig.json vite.config.ts ./

COPY src src
COPY static static

RUN pnpm install
RUN pnpm build

# SERVE!
FROM code.db.cafe/pauloo27/garcom:latest
WORKDIR /app
COPY --from=builder /app/build .
EXPOSE 80
ENTRYPOINT ["garcom", "-port", "80", "-clean-urls", "-error-404", "/app/404.html", "-immutable-dir",  "./_app/immutable"]

Blog powered by SvelteKit, 4 hours of sleep and