Colophon
How was this site built? An honest, thorough breakdown of the technology, the decisions behind it, the infrastructure, and the daily measurement series.
Technology Stack
Framework
- Next.js16.3.6App Router, standalone SSR, Turbopack
- React19.2.3Server Components + use() hook
- TypeScript5.9.3Strict mode
Styling
- Tailwind CSS4.1.18@theme + @custom-variant dark, OKLCH
- CSS VariablesDesign tokens, runtime theme switch
- Apple HIG colorsCanonical iOS Blue (#007AFF / #0A84FF dark)
Animation
- Motion12.38.0LazyMotion + domAnimation strict
- Lenis Smooth ScrollGPU-accelerated scroll
- GPU-only propertiestransform + opacity, layout reflow yok
State
- Zustand5.0.11Hafif client state
- TanStack Query5.100.10Server state + cache
- nuqs2.8.8Type-safe URL search params
Data
- Prisma6.19.2Type-safe ORM
- PostgreSQL16Engagement + analytics
- ResendNewsletter delivery
Quality
- Vitest4.1.11Unit + integration
- Playwright + axe-core1.58.1WCAG 2.2 AA E2E
- Lighthouse CIPerf / a11y / SEO budget checks
Deploy
- Hetzner VPSStandalone Node SSR
- CloudflareCDN + Brotli + 0-RTT + HTTP/3
- Let's EncryptTLS 1.3 + HSTS preload
Observability
- PostgreSQL analyticsFirst-party, consent only, on our own server
- SentryError tracking, EU region
- Real User MonitoringCWV via web-vitals lib
Typography
Design Principles
Performance First
Standalone SSR, ISR, dynamic import, GPU-only animation
Purposeful Motion
Every animation guides the user; none is decorative
Accessible by Default
WCAG 2.2 AA, prefers-reduced-motion, 44×44px targets
Native Dark Mode
Synced with the system setting, custom-variant Tailwind v4
Infrastructure
- Origin
- Hetzner VPS (EU)
- Runtime
- Node v22.23.3
- Process
- systemd (portfolio-ssr.service, User=portfolio)
- CDN
- Cloudflare
- Edge cache
- s-maxage=60
- TLS
- 1.3 + HSTS preload
- DB
- PostgreSQL 16
Chose / Rejected
Six decisions that are live on this server right now, each with the option it replaced, the reason it was needed here, what it costs, and the file and line where you can check it. They are all good tools — these cards are about fit, not ranking.
ChoseMotion v12
RejectedGSAP
Every page is a React tree, and animation state had to live in the same place as component lifecycle.
LazyMotion+domAnimationkeeps the feature bundle out of the first load, and heavier features such as drag stay scoped to the one component that needs them.CostFor timeline-driven, frame-exact choreography GSAP remains stronger; if such a scene is ever needed this decision gets reopened.
Evidence
package.json:64ChoseSF Pro (self-host, woff2)
RejectedInter (Google Fonts)
The whole site is about the Apple ecosystem, so matching the reader's own system typeface is part of the content. The files are served from our own origin through
next/font/local, which removes the third-party font request and its connection cost.CostApple's Personal Use licence: the same choice would not be available in a commercial product, and two variable files (including latin-ext) have to be carried.
Evidence
src/app/(main)/layout.tsx:34ChosePrisma client → src/generated/prisma
RejectedThe default node_modules/@prisma/client path
The standalone SSR output only copies what it can trace. With the client inside the repo under
src/,next buildtreats it as an ordinary module and the whole class of "missing from the.next/standalonecopy" failures disappears.Cost
prisma generatebecomes mandatory on every schema change and the generated directory sits in the repo tree — which is why it is the first step of the prebuild chain.Evidence
prisma/schema.prisma:7Chosesystemd, User=portfolio (non-root)
Rejectedpm2 running as root
The SSR process was taken off root on 14 Jul 2026. The unit ships
NoNewPrivileges,ProtectSystem,ProtectHomeandPrivateTmp;ProtectHomealone means the app can no longer read the deployment credentials under/root. The line above does not hard-code any of this — it asks the unit on every build.CostThe build still runs as root, so
.nextownership has to be handed back after every build;ProtectSystem=strictandMemoryDenyWriteExecuteare deliberately left off because of V8's JIT.Evidence
scripts/generate-colophon-stack.ts:63ChoseTailwind v4
@theme inlinebridgeRejectedUsing shadcn names as they are
The project's colour vocabulary is its own semantic tokens (
--color-bg-primary,--color-text-primary). shadcn's 19 names —bg-muted,text-foregroundand the rest — are bound to those through@theme inline;inlineis required because the theme switches at runtime via the.dark/.admin-v2classes.CostUsing a new shadcn name means adding a bridge line and checking its light/dark contrast before it can be used.
Evidence
src/app/globals.css:42ChoseContent in PostgreSQL (Prisma)
RejectedTypeScript constant arrays
While content lived in TS files every correction required a build, and what the admin panel saw could drift from what the page printed. The database is now the single source; the prebuild step turns those records into static JSON so the page never queries at request time.
CostA content change only reaches production through a build, and the backup burden moved from the repository to the database.
Evidence
scripts/generate-content-json.ts:200
Measurement Series
A cron on this server has been storing a Lighthouse run per page type every night and a PageSpeed Insights mobile run every morning. These are those files, unedited: the highs and the bad days both.
Home
/
View as a table
| Category | Last | Min | Max | Runs |
|---|---|---|---|---|
| Performance | 91 | 53 | 95 | 88 |
| Accessibility | 100 | 100 | 100 | 88 |
| Best Practices | 100 | 100 | 100 | 87 |
| SEO | 100 | 100 | 100 | 88 |
Blog post
/blog/swift-6-neler-yeni/
View as a table
| Category | Last | Min | Max | Runs |
|---|---|---|---|---|
| Performance | 96 | 80 | 98 | 86 |
| Accessibility | 100 | 100 | 100 | 88 |
| Best Practices | 100 | 96 | 100 | 87 |
| SEO | 100 | 100 | 100 | 88 |
Comparison
/comparisons/swiftui-vs-uikit/
View as a table
| Category | Last | Min | Max | Runs |
|---|---|---|---|---|
| Performance | 98 | 94 | 99 | 88 |
| Accessibility | 100 | 97 | 100 | 88 |
| Best Practices | 100 | 96 | 100 | 88 |
| SEO | 100 | 100 | 100 | 88 |
Project
/projects/part-to-time/
View as a table
| Category | Last | Min | Max | Runs |
|---|---|---|---|---|
| Performance | 98 | 59 | 99 | 87 |
| Accessibility | 100 | 96 | 100 | 87 |
| Best Practices | 100 | 100 | 100 | 87 |
| SEO | 100 | 100 | 100 | 87 |
Lighthouse CI · 2026-08-04 → 2026-11-01 · 90 daily files
PageSpeed Insights — 28-day p75 (mobile)
PSI mobile lab, 4x CPU throttled emulation — not field Core Web Vitals.
| URL | Score | LCP | TBT | CLS | n |
|---|---|---|---|---|---|
| / | 90 | 3.30 s | 224 ms | 0.001 | 28 |
| /blog/apple-foundation-models-framework-on-device-llm/ | 87 | 4.35 s | 170 ms | 0.001 | 28 |
| /comparisons/swiftui-vs-uikit/ | 88 | 4.05 s | 219 ms | 0.001 | 28 |
| /quiz/ | 96 | 2.85 s | 112 ms | 0.070 | 28 |
2026-10-05 → 2026-11-01 · 28 daily files
License & Credits
- •Design and content © 2026 Muhittin Çamdalı
- •Icons: Lucide (ISC) + SF Symbols (Apple sistem)
- •Fonts: SF Pro + SF Mono (Apple Personal Use License, self-hosted woff2)
- •For the detailed credits and reference list, see /credits/ page
Infrastructure consulting
Let me set this stack up for your team
Everything on this page — the standalone SSR build, the non-root systemd unit, the token-based theme bridge, the nightly measurement archive — is a setup I install and hand over, documented, on your own servers. If your team is somewhere in the middle of that road, tell me where it is stuck and I will tell you what I would do.
This site is crafted with love and lots of .