Skip to content
Menu
Back to all projects

Open project

ohfork.dev

The site you're currently on.

SOMEHOW WORKSShipped

Started · Updated

  • You're looking at it.This website
The ohfork.dev homepage in dark mode: the headline “Oh fork. I built something again.”, the line “A collection of questionable ideas, happy accidents, and things that somehow work.”, the buttons “Explore the chaos” and “View source”, and the fork logo.

What is it?

My personal playground and portfolio: the projects, the experiments and the jokes, in English and Thai. Polished, editorial design; dry developer humour in the margins.

Why did I build it?

I didn't want a formal portfolio that just lines up screenshots and tech stacks. I wanted a site where people remember both the work and the person who built it.

Can I try it?

You're already using it.

The long version

The briefLink to this section

Look like a design studio, talk like a developer with a sense of humour. The concept document puts a number on it: 80% polished, professional design. 20% unexpected developer humour. The jokes are a bonus, never something you need to get to use the site.

Why I built itLink to this section

I didn't want a formal portfolio that just lines up screenshots and tech stacks. I wanted a site where people remember both the work and the person who built it.

Decisions that shaped itLink to this section

  • Self-hosted, no inbound ports. The site is a Docker container on a home server. It reaches the internet through a Cloudflare Tunnel, an outbound-only connection, so the server opens no ports. Merging to main deploys it.
  • Static by default. Next.js (App Router) prerenders every page, including each project page in both languages. Client JavaScript is for small interactive islands only.
  • English and Thai, properly. English lives at / and Thai at /th. Thai copy is written the way Thai developers actually talk, not translated word for word, and a Thai browser is never redirected. Brand puns such as Oh fork. and WORKS ON MY MACHINE stay in English, because translating a pun kills it.
  • Honesty is enforced by the build. Every project has a canonical status (shipped, in progress, experiment, archived) and an optional joke label. The joke may not contradict the truth, and the content validation fails the build if it does:
export const FLAVOR_STATUSES = {
  "somehow-works": ["shipped", "wip", "experiment"],
  "works-on-my-machine": ["shipped", "wip"],
  "wip-forever": ["wip"],
} as const satisfies Record<StatusFlavor, readonly ProjectStatus[]>;
  • Private stays private. Linking a repository on the private list also fails the build. Anything still missing is marked TODO(owner) and shows up on the page with a dashed outline and a PLACEHOLDER tag, so it can't ship unnoticed.
  • Jokes you can switch off. On the roadmap: a Recruiter Mode that turns every joke off with one switch, and Easter eggs that are opt-in, load lazily, respect reduced motion and never get in the way of finding a project.

Things that were harder (or funnier) than expectedLink to this section

  • The concept, the roadmap and the instructions for the coding agents came before any website. The first commit has no code in it.
  • With Next's Cache Components, a "not found" from a page rendered on demand turned into an empty error page. Unknown project addresses are now sent to the 404 page before rendering starts, so /projects/typo gets the full, helpful 404.
  • Thai has vowels and tone marks stacked above and below the line, so every line height grows on Thai pages to keep them from clipping.
  • The footer only says on a Friday. Pray for me. when the build really happened on a Friday, Bangkok time.
  • This page is in its own showcase, and its demo button says "You're looking at it." instead of linking to itself.