Four CI Stages, Three Frontends, One Domain
TL;DR
- Go backend, Postgres, and three frontends — Remix, Next.js, Hugo — all under one domain on Render.
- GitLab CI runs validate, test, build, deploy; static sites skip the build stage and trigger Render deploy hooks.
- Remix is the front door, proxying to Go and rewriting URLs so the static sites live under the main domain.
- Hugo built 192 pages almost instantly, which is a good argument for static generation.
- Next.js
basePathbreaks image bundling, so images need copying intooutafter the build.
Back on the personal site, refining how the pieces fit together: a Go backend and Postgres, with Remix, Next.js and Hugo on the front.
The deployment shape
Everything deploys to Render, driven by GitLab CI in four stages — validate, test, build, deploy.
The useful asymmetry is that the static sites don’t need GitLab to build them. Hugo and Next.js get validated and tested in the pipeline, then GitLab triggers Render’s deploy hooks and Render does the build itself using custom commands and environment variables. Only the containerised services actually consume GitLab build minutes.
That’s deliberate. Running only the jobs that need running keeps compute time and cost down, and it’s the sort of thing worth getting right early because pipeline waste compounds quietly.
Remix as the front door
Remix is the primary interaction layer. It proxies to the Go backend and rewrites URLs so everything resolves under the main domain path, which is what lets two static sites living in separate containers appear as parts of one site.
I also reconfigured Hugo to use relative URLs, which is what makes that link rewriting work — absolute URLs baked at build time would fight the proxy.
Hugo built 192 pages remarkably fast. Static site generation earns its reputation.
The Next.js basePath problem
basePath is necessary — it’s part of the URL rewrite that replaces the host and makes navigation work under the shared domain. But it breaks image bundling. Images don’t end up where the build expects them, so they don’t reach their destination.
The fix is a post-build script: Next.js emits to an out directory, and the script copies image files into out afterwards. Not elegant, but the alternative is giving up the base path, and the base path is what makes the whole single-domain arrangement work.
Notes to self
Front matter YAML has to be exactly right. A parsing error in a single post breaks the build, which is a sharp edge given the posts are machine-generated.
The GitLab CI Docker login commands are deprecated and I’m leaving them for now. Velocity over tidiness while the whole thing is still moving.
Connect is too complex as a Next.js app for what it does. Vue is the likely replacement — I need to confirm Vue exports a static site cleanly so it drops into the same deployment model.
ryer.io