kyle.pericak.com is a statically exported Next.js 14 site. Markdown files are processed at build time into HTML pages. The site is deployed as static files with no server-side rendering at runtime.
The site is hosted as static files in a Google Cloud Storage bucket
(gs://kyle.pericak.com). There's no application server in production.
Cloudflare sits in front as a CDN and handles HTTPS termination and
DNS proxy.
The GCP project is kylepericak in northamerica-northeast1.
GCS supports serving a bucket as a static website when the bucket name matches the domain name exactly. The setup has three layers:
GCS bucket (gs://kyle.pericak.com). The bucket name must match
the fully qualified domain. GCS automatically serves index.html as
the default page for the bucket root and for directories. The bucket
is configured for static website hosting with MainPageSuffix set to
index.html. GCS serves the content over plain HTTP on
http://c.storage.googleapis.com/kyle.pericak.com/ (or via the
CNAME-compatible endpoint http://kyle.pericak.com.storage.googleapis.com).
DNS (Cloudflare). A CNAME record in Cloudflare points
kyle.pericak.com to c.storage.googleapis.com. Cloudflare's DNS
proxy mode is enabled (orange cloud), so requests hit Cloudflare's
edge first rather than going directly to GCS.
Cloudflare CDN / HTTPS. Because the DNS proxy is enabled,
Cloudflare terminates TLS (provides the HTTPS certificate
automatically), caches responses at the edge, and forwards
cache-miss requests to GCS over HTTP. The Cache-Control headers
set during deployment (no-cache,no-store,must-revalidate on changed
files) tell Cloudflare to revalidate on every request, so updates
appear immediately after a deploy without a manual cache purge.
To spin up another static site using the same mechanism:
gs://other.example.com). Enable static website hosting:
gsutil mb -p kylepericak -l northamerica-northeast1 gs://other.example.com
gsutil web set -m index.html -e 404.html gs://other.example.com
gsutil iam ch allUsers:objectViewer gs://other.example.com
c.storage.googleapis.com. If using Cloudflare, enable
the proxy (orange cloud) for automatic HTTPS and caching.gsutil rsync (same pattern as
bin/prod-deploy.sh).cloudbuild.yaml pattern.Deployment happens two ways: automated via Cloud Build, or manually
via bin/prod-deploy.sh.
A Cloud Build trigger (apps-blog-trigger) fires on pushes to main
that touch apps/blog/**. The pipeline is defined in
apps/blog/cloudbuild.yaml and managed by Terraform in
apps/blog/tf/cloudbuild.tf.
The pipeline has three steps:
gcr.io/$PROJECT_ID/kylepericakdotcom) from apps/blog/Dockerfile.
The image is Ubuntu Noble with Node.js and npm. It copies the blog
source and .git directory (needed for the git ref in the footer),
then runs npm install.bin/build-blog-files.sh inside the
container to produce the static HTML output.gsutil -m rsync -r -c -d to sync the built
output to gs://kyle.pericak.com. The -d flag deletes files in
the bucket that aren't in the build output.bin/prod-deploy.sh does the same gsutil rsync from a local build.
It checks that blog/out/index.html exists first, runs a dry-run to
identify changed files, syncs them, then sets Cache-Control: no-cache,no-store,must-revalidate on changed files so Cloudflare
picks up updates immediately.
Only Kyle runs manual deploys.