Notes · Architecture

How this site is built

The architecture behind nicklosier.com — the domain, the hosting, the build, and how the source stays private while the site stays public. Written to be reusable by anyone who owns a domain and wants to put a real site on it.

The shape of it

GoDaddy            domain registration + DNS
   |  A records -> GitHub Pages edge
   v
GitHub Pages       static hosting + automatic TLS
   ^
   |  deploys the build artifact
GitHub Actions     runs on every push to main
   ^
   |  builds
Private repo       Vite + React + Tailwind source

Four moving parts, none of them a server. Running cost is the domain renewal. Nothing here needs patching, and the site will still be up and unchanged in three years.

Domain and DNS at GoDaddy

The domain is registered at GoDaddy and its DNS stays there. GitHub only needs to be pointed at, not delegated to.

For an apex domain — example.com, no www — add four A records, all with host @:

185.199.108.153
185.199.109.153
185.199.110.153
185.199.111.153

Then one CNAME record, host www, pointing at username.github.io.

Two GoDaddy-specific notes. Do not use GoDaddy's Domain Forwarding to reach the site; it breaks TLS and the address bar, and Pages will not issue a certificate through it. And GoDaddy ships a parked-page A record on @ by default — delete it, or it competes with the four above.

Once DNS resolves, tick Enforce HTTPS in the repo's Pages settings. GitHub issues the certificate automatically, which takes somewhere between a few minutes and an hour. If the checkbox is greyed out, DNS has not propagated yet. Wait rather than changing things.

Hosting on GitHub Pages

Pages serves static files from a repository, free, with TLS included.

Deploy with GitHub Actions, not the legacy branch mode. The old approach publishes a gh-pages branch exactly as committed, which means committing build output. The current approach runs a workflow: check out, npm ci, npm run build, upload the built folder as an artifact, deploy it. Build output never enters the repo.

Turning Pages on for a repo that never had it needs an owner credential — the Actions token can configure a site but cannot create one:

gh api -X POST repos/OWNER/REPO/pages -f build_type=workflow

After that the workflow's configure-pages step is a harmless no-op.

Put a CNAME file in the build's static folder. For Vite that means public/CNAME, containing one line with the domain. This is the tip worth the whole page. GitHub stores the custom domain in a CNAME file at the root of the published output. If that file exists only because the domain was typed into the Pages settings UI, the next deploy overwrites the output, silently drops the domain, and the site falls back to username.github.io with a certificate warning. Keeping CNAME in public/ means every build recreates it.

How the site itself is built

Vite + React + Tailwind, built as a multi-page app rather than a single-page one. Each page gets its own entry in vite.config.js, its own .html, and a small mount shim, so the pages are genuinely separate documents rather than client-side routes. That keeps them independently shareable and independently indexable.

Set the Vite base to / when serving from the apex of your own domain. If it ever moves back to a username.github.io/repo project path, base has to become /repo/ or every asset 404s. That is the most common way a working Pages site breaks on a move.

Not every page needs the framework. This one is plain HTML with inline styles, dropped into public/, which Vite copies through untouched. A page with no interactive behaviour does not need to be a build entry.

The résumé is a static file — public/resume.pdf, served at /resume.pdf. It is generated from LaTeX, so the PDF and the site never disagree about dates.

Keeping the source private while the site is public

These are two different questions, and conflating them is where people get caught out.

The repository is private. The published output is public. Pages serves whatever the build produces to anyone, with no authentication. Publishing from a private repo requires a paid GitHub plan (Pro or above); from a public repo it is free.

A committed file is exposed whether or not the site links to it. If the repo is public, moving an image out of public/ is not enough — it has to leave the tracked tree. Deleting it in a later commit is not enough either, since the history still has it.

Anything sensitive lives in a gitignored directory. This project keeps a .private/ folder at the repo root: inside the working tree for convenience, gitignored so it never enters git history. Being ignored is the only protection, so nothing in it is force-added or copied into a tracked path.

Scan before publishing, over the build and the tracked source. Scanning only the built output misses everything that is committed but not bundled — notes, scripts, docs. Both need checking. If the material originated somewhere with confidentiality obligations, keep the list of strings you are scanning for outside the repo as well; a checked-in list of things to redact is itself a leak of those things.

Keeping pages unlisted from search engines

Some pages are written for one reader and should not turn up in a search for your name. Unlisted is not the same as secret — anyone with the link can read it, which is fine for a résumé.

Two mechanisms, and one trap: