PERSONAL PORTFOLIO

Published on May 27, 2026
Next.jsTypeScriptCloudflare Worker

A portfolio site built to showcase projects and blog posts that I write in Notion. I built a custom UI design with two distinct visual themes, keeping everything fast on edge infrastructure, and poured my personality into it. This post covers the architecture, the tradeoffs behind each decision, and what worked or not.

Here is a screenshot of my Notion CMS for my blog:

Notion blog

During the development of this personal portfolio, I built quite a lot of features. Here are some highlights of what I shipped and improved:

0x
FASTER

Notion API took ~3s to load. After caching them at the edge, they load in <50ms*.

*p50: 41ms, n=100 in prod

0+
UI COMPONENTS

Every UI component built from scratch. Full control over every color, shape, and animation.

0
THEMES

Designed a dual theme system. Every component is theme-token driven, making development easy.

THE PHILOSOPHY

"Any sufficiently advanced technology is indistinguishable from magic."

- Arthur C. Clarke

We use technology every day without really thinking about how it works. Most of the time, we just trust that it does. But when I started digging deeper into it, I became more and more interested in how much complexity is hidden underneath. A lot of things are abstracted away so well that, from the outside, it almost feels like magic, the kind portrayed in fantasy movies or games. Even after understanding more of it, that feeling never disappears.

And for that reason, I decided to build this personal portfolio as a way to share the magic behind the scenes. To pull back the curtain and show how all the pieces fit together, from the tech stack to the architecture decisions, and even the mistakes and learnings along the way. My hope is that by sharing this, you can learn from my journey and maybe even be inspired to build your own projects.

The floating button on this website is called VeilButton. It's named that way to work as a button to unveil the magic behind the scenes. That's the philosophy of the duality between the magic and cyber themes on this website.

MAGIC

purple · runes · spell circles

CYBER

cyan · circuits · PCB traces

THE STACK

Here are the technology stacks I used to develop this project. Special shoutout to Claude Code for helping me speed this up significantly. You're still expensive though.

FRONTEND

PRESENTATION

03 ITEMS
Next.js15
Tailwind CSSv3
Framer Motion

BACKEND

SERVICE

02 ITEMS
Next.js15
Cloudflare Worker

INFRASTRUCTURE

EDGE

04 ITEMS
Cloudflare Workers
Workers KV
Rate Limiting
Edge SecurityWAF · Bot · SSL · DNS

You may check the details on each part of the stack in the backend and frontend sections below. I combined the infrastructure and backend layers into one section since they are closely related in this project.

BACKEND

The backend is built entirely on Cloudflare's edge infrastructure. This website is deployed as a serverless worker, and there is another worker that pulls blog content from Notion on a schedule and caches it in Workers KV, so the site never waits on Notion at request time. I also put a basic rate limiter and a WAF at the edge layer to keep bad actors from spamming this site. All of those pieces together make the backend blazingly fast.

The personal portfolio is deployed as a Cloudflare Worker via OpenNext. Blog content flows from Notion into Workers KV via the notion-blog-worker, then fetched by the portfolio worker, and finally served to the visitor.

ARCHITECTURE / personal-portfolio

DATA FLOW

NOTION API

api.notion.com

CLOUDFLARE

NOTION BLOG WORKER

api.alvinwilta.com

PERSONAL PORTFOLIO

alvinwilta.com

VISITOR

browser

Blog data source can be switched between Notion API and Worker API via env vars.

The notion-blog-worker runs on a separate Cloudflare Worker. Every hour, a cron job fetches all posts from Notion, resolves tags and child blocks, builds a posts index, and writes everything to Workers KV. Reads are served straight from KV, so Notion is never touched on the read path. All read endpoints require an X-API-Key header, keeping the worker private.

ARCHITECTURE / notion-blog-worker

WRITE PATH

CLOUDFLARE TRIGGER

cron 0 * * * * (hourly)

NOTION API

api.notion.com

notion-blog-worker

cron handler

· resolve tags + resources

· fetch child blocks

· build posts:index

· cleanup stale slugs

WORKERS KV

posts:index, post:{slug}

READ PATH

CLIENT

browser fetch

notion-blog-worker

fetch handler

/blog/posts

/blog/posts/:slug

/blog/tags

WORKERS KV

cacheTtl: 3600s

Both Read and Write paths require an X-API-Key for authentication.

Here are some backend implementation highlights:

PERFORMANCE~75x faster

EDGE-CACHED BLOG API

Notion's API averages 3065ms per request and has strict rate limits. A Cloudflare Worker syncs all blog content to the edge every hour so page loads never wait on Notion. Response time drops to p50: 41ms (n=100 production requests).

  • [✓]

    CQRS-lite

    write path / read path split

  • [✓]

    Materialized read model

    posts:index · post:{slug}

  • [✓]

    KV + HTTP cache

    cacheTtl · max-age = 3600s

  • [✓]

    API key auth

    X-API-Key on reads

  • [✓]

    Cron-driven sync

    0 * * * * · stale-slug GC

PERFORMANCE<5ms boot
container/vm~1s
edge isolate<5ms

GLOBAL EDGE DELIVERY

Responses come from the edge closest to each visitor. No central server round-trip, no cold start, low latency worldwide.

SECURITY120/min
NORMAL
✓ allowed
BURST
✕ blocked
threshold120/min per IP → HTTP 429

EDGE RATE LIMITING

Burst traffic gets an HTTP 429 at the edge before reaching the Worker. Real visitors see nothing, origin stays cheap.

TOOLINGBENCHMARK

BENCHMARK ENDPOINT

Click the live demo to compare Notion against the cached Worker side by side. It fetches both the Notion API and blog API in parallel, up to 5 runs to show real variance.

FRONTEND

The frontend is built with Next.js 15 and Tailwind CSS. Every component is built from scratch, with a theme-aware registry that swaps magic and cyber variants automatically. Blog content is rendered via a custom Notion block renderer with no external dependency.

Here are some frontend implementation highlights:

CONTENT16 blocks

NOTION CONTENT RENDERER

Built from scratch to render every Notion block type: text, headings, code, callouts, toggles, images, columns, video. No external renderer dependency.

💡
IMG

CUSTOM UI LIBRARY

Buttons, cards, icons, and decor built from scratch. Even had to create custom renderer for the spells.

UI2 modes
magic
cyber

THEME-AWARE COMPONENT REGISTRY

Most of the decorations and components have both a magic and cyber version. One hook call returns the right one for the current theme.

PLAYGROUNDENTER FORGE

SPELL FORGE

Interactive builder for the site's geometric SVG system. Tweak rings, polygons, rotation, and runes live, then export the result as SVG.

KEY DECISIONS

Every project has decision points where multiple reasonable paths exist. These are the ones that shaped how I built this site, what I picked, why, and the tradeoffs I accepted.

LEARNINGS

Not everything went smoothly. Some decisions I made paid off immediately, others cost me more than expected. These are the honest takeaways from building this.

OKWORKED01

I already have a worker starter kit that I created a few years ago, so to add notion-blog-worker I only had to implement a simple API handler to connect to Notion.

→ next:Invest in reusable scaffolding. It compounds across every future project. Using AI for boilerplate code is a waste of tokens.
OKWORKED02

I built the benchmark endpoint to verify the cache was actually faster. It ended up as a public page that proves the architecture decision.

→ next:Build measurement before the thing being measured, not after.
!!GOTCHA03

I underestimated this project because it was a personal project, so I never wrote a feature list or roadmap. Scope ballooned from minor tweaks into optimizations. Site ended up solid, but took far longer than needed.

→ next:Use proper roadmaps and feature specs to keep scope in check, even on personal projects.
!!GOTCHA04

I changed the theme of this website at least three times before settling on the current one. So each time I changed the theme, I had to rewrite every single component to support the new theme's aesthetic.

→ next:Draw or design a barebone mockup before implementing any code.

Thank you for visiting my personal web portfolio. I hope this post inspires you to build something personal and interesting!

GOT THOUGHTS?

Questions or critique are welcome. Feel free to reach out!

GET IN TOUCH
BACK