[{"data":1,"prerenderedAt":638},["ShallowReactive",2],{"navigation":3,"/projects/old-web-diary":14,"/projects/old-web-diary-surround":627},[4],{"title":5,"path":6,"stem":7,"children":8,"page":13},"Blog","/blog","blog",[9],{"title":10,"path":11,"stem":12},"My Experience as a Junior Software Engineer (First Year)","/blog/my-experience-as-a-junior-software-engineer","blog/my-experience-as-a-junior-software-engineer",false,{"id":15,"title":16,"body":17,"buttonLabel":607,"date":608,"description":609,"extension":610,"image":611,"meta":612,"navigation":613,"path":614,"seo":615,"stem":616,"tags":617,"__hash__":626},"projects/projects/old-web-diary.md","Oldweb Diary",{"type":18,"value":19,"toc":590},"minimark",[20,25,29,35,38,42,45,50,53,58,61,66,69,74,77,82,85,87,91,94,100,106,112,132,145,147,151,156,208,212,217,248,265,270,285,309,314,325,342,347,358,380,382,386,389,427,434,436,440,501,503,507,512,522,527,542,547,550,555,558,560,564,571,574,576,580],[21,22,24],"h2",{"id":23},"the-idea","THE IDEA",[26,27,28],"p",{},"The early internet was weird, wonderful, and almost entirely undocumented in one place. Rickroll origin stories, Flash animations nobody saved, GeoCities pages that vanished overnight, and the existential dread of the MySpace Top 8.",[26,30,31,34],{},[32,33,16],"strong",{}," is a digital museum dedicated to preserving that era — built as a full-stack web app styled to look and feel like the operating systems people were actually using at the time.",[36,37],"hr",{},[21,39,41],{"id":40},"the-product","THE PRODUCT",[26,43,44],{},"A fully functional nostalgia platform with five main sections:",[26,46,47],{},[32,48,49],{},"1. Meme Museum",[26,51,52],{},"50+ memes with full origin stories, era classification, tags, like counts, view counts, and a related memes section. Each meme has its own detail page with dynamic OG images for social sharing.",[26,54,55],{},[32,56,57],{},"2. This Day in Internet History",[26,59,60],{},"A date-matched events engine that shows what happened on today's date across internet history. Falls back to featured events when no match exists for the current date.",[26,62,63],{},[32,64,65],{},"3. Lost Media Vault",[26,67,68],{},"An archive of Flash animations, viral videos, old websites, and browser games that nearly disappeared. Each item is tagged as Available, Partially Lost, or Fully Lost, with related media suggestions for internal discovery.",[26,70,71],{},[32,72,73],{},"4. Memory Wall",[26,75,76],{},"A community submission system where users share their earliest internet memories. Includes a moderation queue, platform tags, like functionality, and pagination.",[26,78,79],{},[32,80,81],{},"5. Retro Assets Library",[26,83,84],{},"A downloadable collection of Win95 sounds, animated cursors, wallpapers, icons, and bitmap fonts from the era.",[36,86],{},[21,88,90],{"id":89},"the-retro-os-theme-system","THE RETRO OS THEME SYSTEM",[26,92,93],{},"This was the defining design challenge of the project. The entire UI is skinned to look like one of three operating systems — and users can switch between them at any time.",[26,95,96,99],{},[32,97,98],{},"Win95:"," Teal desktop, grey windows, navy titlebars, inset/raised border shadows, MS Sans Serif typography.",[26,101,102,105],{},[32,103,104],{},"WinXP:"," Bliss-inspired CSS gradient wallpaper, warm off-white windows, blue gradient titlebars, colored window buttons (yellow/green/red), rounded Start button.",[26,107,108,111],{},[32,109,110],{},"Mac OS9:"," Platinum windows, gradient titlebars, left-side close button with centered title, SVG minimize/maximize icons, Chicago font, Apple Menu in system tray.",[26,113,114,115,118,119,123,124,127,128,131],{},"The skin system uses ",[32,116,117],{},"CSS custom properties"," (",[120,121,122],"code",{},"--skin-*"," variables) so every component automatically adapts to any theme without conditional rendering. Switching themes updates a single ",[120,125,126],{},"data-skin"," attribute on the ",[120,129,130],{},"\u003Chtml>"," element.",[26,133,134,137,138,141,142,144],{},[32,135,136],{},"Zero theme flash on page load"," — achieved by reading the user's saved theme from an HTTP cookie server-side in ",[120,139,140],{},"layout.tsx"," and rendering the correct ",[120,143,126],{}," in the initial HTML response. No JavaScript needed for the first paint.",[36,146],{},[21,148,150],{"id":149},"the-technical-deep-dive","THE TECHNICAL DEEP DIVE",[152,153,155],"h3",{"id":154},"stack","Stack",[157,158,159,166,172,178,184,190,196,202],"ul",{},[160,161,162,165],"li",{},[32,163,164],{},"Framework:"," Next.js 16 (App Router, Turbopack)",[160,167,168,171],{},[32,169,170],{},"Language:"," TypeScript throughout",[160,173,174,177],{},[32,175,176],{},"API layer:"," tRPC v11 with type-safe client/server",[160,179,180,183],{},[32,181,182],{},"ORM:"," Prisma v5 with PostgreSQL (Supabase)",[160,185,186,189],{},[32,187,188],{},"Caching / counters:"," Upstash Redis",[160,191,192,195],{},[32,193,194],{},"Media storage:"," Cloudflare R2",[160,197,198,201],{},[32,199,200],{},"Deployment:"," Vercel (Singapore region)",[160,203,204,207],{},[32,205,206],{},"Monitoring:"," Sentry + PostHog",[152,209,211],{"id":210},"hardest-technical-problems","Hardest Technical Problems",[26,213,214],{},[32,215,216],{},"Problem 1: OG Image Generation in Edge Runtime",[26,218,219,220,223,224,227,228,231,232,235,236,239,240,243,244,247],{},"Each meme detail page generates a dynamic social share card via ",[120,221,222],{},"@vercel/og",". The challenge: the edge runtime doesn't have Node.js APIs (",[120,225,226],{},"Buffer",", ",[120,229,230],{},"Uint8Array"," workarounds required), ",[120,233,234],{},".webp"," images fail silently, ",[120,237,238],{},"ImageResponse"," crashes on JSX ",[120,241,242],{},".map()"," calls, and ",[120,245,246],{},"boxShadow"," is unsupported.",[26,249,250,253,254,257,258,261,262,264],{},[32,251,252],{},"Solution:"," Split into two routes — an edge runtime OG renderer that fetches image data from a separate Node.js route that handles Prisma. Used ",[120,255,256],{},"Array.from(bytes).map().join('')"," + ",[120,259,260],{},"btoa()"," for edge-safe base64 encoding. Skipped ",[120,263,234],{}," files entirely (showed title placeholder instead).",[26,266,267],{},[32,268,269],{},"Problem 2: Theme Flash Elimination",[26,271,272,273,276,277,280,281,284],{},"Initial approach — localStorage + ",[120,274,275],{},"useEffect"," — caused a visible flash of Win95 theme before the correct theme loaded. Several intermediate approaches failed (inline ",[120,278,279],{},"\u003Chead>"," script, ",[120,282,283],{},"useState"," lazy initializer) because React's hydration mismatch warnings persisted and the flash remained.",[26,286,287,290,291,294,295,297,298,300,301,304,305,308],{},[32,288,289],{},"Final solution:"," Cookie-based SSR. The server reads the ",[120,292,293],{},"oldweb-skin"," cookie in ",[120,296,140],{}," before rendering, sets ",[120,299,126],{}," in the HTML response, and passes ",[120,302,303],{},"initialSkin"," as a prop to ",[120,306,307],{},"SkinProvider",". The browser receives the correct theme in the first byte of HTML — JavaScript is never involved in the initial render.",[26,310,311],{},[32,312,313],{},"Problem 3: Prisma Sort by Month/Day",[26,315,316,317,320,321,324],{},"The admin events panel needed events sorted featured-first, then by calendar day (January before December) — not by year. Prisma's ",[120,318,319],{},"orderBy"," can't express ",[120,322,323],{},"EXTRACT(MONTH FROM eventDate)",".",[26,326,327,329,330,333,334,337,338,341],{},[32,328,252],{}," Raw SQL query using ",[120,331,332],{},"$queryRaw"," with ",[120,335,336],{},"EXTRACT(MONTH)"," and ",[120,339,340],{},"EXTRACT(DAY)"," ordering, properly typed with Prisma's raw query type system.",[26,343,344],{},[32,345,346],{},"Problem 4: ESLint Rule Compliance Without Breaking Functionality",[26,348,349,350,353,354,357],{},"Nine ESLint errors across the codebase — all ",[120,351,352],{},"setState in useEffect"," patterns in admin form components, and a ",[120,355,356],{},"window.location.hash"," mutation error in the tab component.",[26,359,360,362,363,365,366,369,370,372,373,376,377,379],{},[32,361,252],{}," Moved all state initialization to ",[120,364,283],{}," lazy initializers and event handler inline logic. Replaced the hash mutation in ",[120,367,368],{},"AdminTabs"," with a ",[120,371,275],{}," that watches ",[120,374,375],{},"active"," state and writes to ",[120,378,356],{}," from within an effect — satisfying the immutability rule while preserving tab persistence behavior.",[36,381],{},[21,383,385],{"id":384},"the-admin-cms","THE ADMIN CMS",[26,387,388],{},"A password-protected 6-tab admin panel at a secret URL, built entirely without a third-party CMS:",[157,390,391,397,403,409,415,421],{},[160,392,393,396],{},[32,394,395],{},"Memes"," — create, edit, delete, feature/unfeature with auto-slug generation",[160,398,399,402],{},[32,400,401],{},"Events"," — full CRUD, sorted by featured-first then month/day ascending",[160,404,405,408],{},[32,406,407],{},"Memories"," — approve or reject pending community submissions",[160,410,411,414],{},[32,412,413],{},"Lost Media"," — create and manage vault entries with status classification",[160,416,417,420],{},[32,418,419],{},"Retro Assets"," — manage downloadable asset library",[160,422,423,426],{},[32,424,425],{},"Site News"," — post and hide announcements shown on the homepage",[26,428,429,430,433],{},"Authentication uses ",[32,431,432],{},"iron-session"," with HTTP-only cookies and a secret admin URL. The session cookie is set on login and cleared on logout — a subtle bug where the logout route was creating the session on a different response object than was returned (causing the cleared cookie to be discarded) was the trickiest auth fix in the project.",[36,435],{},[21,437,439],{"id":438},"seo-setup","SEO SETUP",[157,441,442,449,455,461,467,470,477,487,494],{},[160,443,444,445,448],{},"Dynamic ",[120,446,447],{},"generateMetadata"," per meme and lost-media page (title, description, OpenGraph, Twitter card)",[160,450,451,452],{},"Dynamic OG images per meme via ",[120,453,454],{},"/api/og-meme/[slug]",[160,456,457,460],{},[120,458,459],{},"sitemap.xml"," auto-generated from all meme + lost-media slugs via Prisma",[160,462,463,466],{},[120,464,465],{},"robots.txt"," blocking admin path and API routes, pointing to sitemap",[160,468,469],{},"Canonical URLs on all dynamic pages",[160,471,472,473,476],{},"JSON-LD ",[120,474,475],{},"Article"," schema on meme and lost-media detail pages",[160,478,472,479,482,483,486],{},[120,480,481],{},"WebSite"," schema with ",[120,484,485],{},"SearchAction"," on homepage",[160,488,489,490,493],{},"Descriptive alt text via a shared ",[120,491,492],{},"altText.ts"," utility (e.g. \"Rickroll — viral internet meme from 2007, mid-2000s\")",[160,495,496,497,500],{},"PageSpeed Insights: ",[32,498,499],{},"100 SEO, 100 Best Practices"," on both mobile and desktop",[36,502],{},[21,504,506],{"id":505},"what-this-taught-me","WHAT THIS TAUGHT ME",[26,508,509],{},[32,510,511],{},"1. CSS custom properties are the right tool for theming at scale",[26,513,514,515,518,519,521],{},"Three completely different OS aesthetics from a single component library — no conditional rendering, no theme context drilling. Just ",[120,516,517],{},"var(--skin-*)"," variables and a ",[120,520,126],{}," attribute on the root element. Every new component automatically inherits the correct theme.",[26,523,524],{},[32,525,526],{},"2. Edge runtime has sharp edges",[26,528,529,531,532,534,535,534,537,534,539,541],{},[120,530,222],{}," is powerful but has real constraints that only surface in production: no ",[120,533,226],{},", no ",[120,536,234],{},[120,538,246],{},[120,540,242],{}," in JSX. Developing and testing in localhost masked these completely. The lesson: for edge functions, test in a Vercel preview deployment early.",[26,543,544],{},[32,545,546],{},"3. Cookie-based SSR is the correct solution for theme persistence",[26,548,549],{},"localStorage-based theming always causes a flash. The right mental model is: if you need something to affect the server-rendered HTML, it has to come from the server — and the only client data the server can see is cookies.",[26,551,552],{},[32,553,554],{},"4. Full-stack solo projects reveal the gaps you didn't know you had",[26,556,557],{},"Building the admin CMS, the authentication system, the Redis hit counter, the OG image pipeline, the SEO layer, and the deployment config — all on the same project — forces you to develop opinions about every layer of the stack. That breadth is worth more than going deep on any single piece.",[36,559],{},[21,561,563],{"id":562},"the-result","THE RESULT",[26,565,566,567,570],{},"A live, production site at ",[32,568,569],{},"oldwebdiary.com"," — fully deployed on Vercel with a custom domain, Supabase PostgreSQL database, Cloudflare R2 media storage, and Upstash Redis hit counter.",[26,572,573],{},"50+ memes documented. 50+ internet history events. 13 lost media items. 20 retro assets. A community memory wall. Three fully functional OS themes. Zero compromises on the nostalgia.",[36,575],{},[21,577,579],{"id":578},"visit-website","VISIT WEBSITE",[26,581,582],{},[32,583,584],{},[585,586,569],"a",{"href":587,"rel":588},"https://oldwebdiary.com/",[589],"nofollow",{"title":591,"searchDepth":592,"depth":592,"links":593},"",2,[594,595,596,597,602,603,604,605,606],{"id":23,"depth":592,"text":24},{"id":40,"depth":592,"text":41},{"id":89,"depth":592,"text":90},{"id":149,"depth":592,"text":150,"children":598},[599,601],{"id":154,"depth":600,"text":155},3,{"id":210,"depth":600,"text":211},{"id":384,"depth":592,"text":385},{"id":438,"depth":592,"text":439},{"id":505,"depth":592,"text":506},{"id":562,"depth":592,"text":563},{"id":578,"depth":592,"text":579},null,"2026","A digital museum of early internet culture. Built a full-stack nostalgia platform with three retro OS themes, 50+ meme origin stories, This Day in Internet History, a Lost Media Vault, and a community Memory Wall.","md","/projects/oldweb-diary.png",{},true,"/projects/old-web-diary",{"title":16,"description":609},"projects/old-web-diary",[618,619,620,621,622,623,624,625],"Next.js","TypeScript","tRPC","Prisma","Supabase","Tailwind CSS","Cloudflare R2","Vercel","fY7yeKWKaBg6228tzJuRxOb9FIVMbT86bRLW5sqclQw",[628,633],{"title":629,"path":630,"stem":631,"description":632,"children":-1},"Website Performance Optimization","/projects/homepage-revamp","projects/homepage-revamp","25%+ PageSpeed improvements across multiple sites. Server-side rendering, code optimization, and modern UI/UX. Fast delivery without technical debt.",{"title":634,"path":635,"stem":636,"description":637,"children":-1},"Splitlog — Personal Run Management Platform","/projects/splitlog","projects/splitlog","I was tired of copy-pasting my Strava stats into Notion after every run. So I built my own platform to automate it. First full-stack project. First time with Next.js, Supabase, and real API integrations. The moment my run synced automatically? That's when it clicked.",1782865071666]