Skip to main content

Behind the Scenes

How This Site Is Built

This portfolio isn't on autopilot hosting. I package it, ship it, and run it myself on one small server — and it still loads fast anywhere in the world. Here is the whole system in one picture.

Youyour browser
Cloudflarea fast copy near you
nginxsecure entry (HTTPS)
My serverNext.js in a container

Almost every visit is answered at the edge, close to the visitor. My server is only touched when the edge doesn't already have the page.

Self-hosted
no managed platform
1 vCPU · 512 MB
the whole server
1,000+
pages, prebuilt
~106 kB
JavaScript per visit

I run it myself

No managed hosting. The site lives in a container on a small server I control from top to bottom — one predictable cost, nothing hidden behind a platform.

Self-hosted on a VPS

Every update ships itself

When I push a change, an automated pipeline tests it, packages it, and deploys it. If anything fails the checks, it never reaches the live site.

Automated CI/CD

Fast by design

Pages are built ahead of time and served from servers close to each visitor, so the site stays quick even on a single-CPU machine.

Prebuilt + edge-cached

For engineers

The System in Depth

Every part of the build, with the real numbers and trade-offs behind it. Pick a topic.

The Stack, by Layer

Every layer is something I run and can reason about end to end — not a black box a platform hides from me.

Framework

Next.js 15 App Router

React Server Components and route-level metadata, prerendered to static HTML at build time.

i18n

next-intl (en / id)

Full bilingual routing with correct hreflang alternates; setRequestLocale keeps every route static.

Styling

Tailwind + CSS variables

Utility-first classes over a small token layer that drives the dark theme and orange accent.

Motion

framer-motion

Used surgically — scroll reveals only where they help; pure-CSS fades everywhere else to keep JS off the route.

Packaging

Docker + GHCR

A standalone image built in CI and pushed to GitHub's container registry, versioned by commit SHA.

CI/CD

GitHub Actions

Typecheck, lint, build, push, and SSH deploy — a red pipeline never reaches production.

Edge

Cloudflare + nginx

Cloudflare caches at the edge; nginx terminates TLS and reverse-proxies to the container on 127.0.0.1.

Observability

Grafana · Prometheus · Loki

Metrics, logs, and alerting for this box and the client servers beside it — one self-hosted pane of glass.

Services

Groq + Resend

The AI chat assistant runs on Groq; the contact form delivers server-side through Resend.

FAQ

Frequently Asked Questions

Short answers to what people ask most about how this site runs.

Is this site hosted on Vercel?

No. It started on Vercel but is now self-hosted: a standalone Next.js Docker image runs in a 1 vCPU / 512 MB container on a small VPS, fronted by Cloudflare, with nginx terminating TLS. The move gave one predictable monthly cost and full control of the runtime.

How is the site deployed?

Every push to main runs a GitHub Actions pipeline: typecheck and lint, then build a Docker image, push it to GHCR tagged by commit SHA, and deploy over SSH with docker compose. The container is replaced in place only after its healthcheck passes.

Why does the site use WebP instead of AVIF for images?

On a 1 vCPU box, encoding a cover to AVIF measured 4–9 seconds versus about 0.6 seconds for WebP — a 7–16x penalty — and Cloudflare does not yet cache the image optimiser, so the origin pays that cost repeatedly. WebP wins until the edge caches optimised variants.

How does the site stay fast on a single small VPS?

Every page is statically prerendered at build time, Cloudflare absorbs repeat traffic at the edge, the client i18n payload was cut from 1.6 MB to 76 KB, and the animated footer is roughly 4 kB of hand-written WebGL instead of a 3D library.

How is the site monitored?

It reports into a self-hosted Grafana, Prometheus, and Loki stack alongside the client servers beside it, with alert rules that escalate to a phone call. A Docker healthcheck probes the container every 30 seconds and uptime-kuma checks the public URL externally.

If I treat my own site this way, imagine your project.