Dev blog by Jaeyong Choi (jaeyonging), a frontend and full-stack developer. I write about web development with React, TypeScript and Node.js, the sites I built myself, and my project notes.
100 days of a monitoring server I built myself: what I did well and what needs fixing
I went back through the records of 270,000 checks. Checking the public domain was a good call. Self-monitoring, sequential checks, endless history and tokens in logs need fixing.
My health check recorded for 3 months and never told anyone
31 failures piled up with 0 alerts. Alerting on every failure meant 58 emails, 2 in a row meant 4. I counted from the records first, then added alerts on state change.
I deleted the guest login and built a status page
I kept adding guest permission branches to show people my dashboard, then saw the only thing worth showing was "is it alive" and built a separate public status page.
PM2 revived my apps so well that I couldn't see the outages
The health check kept returning 200 while the restart count sat at 96. Why crash loops should be judged by the increase within a window, not the cumulative total.
I almost added Redis, measured first, and didn't. The real culprit was a cache header
A server with a 6ms TTFB didn't need a cache layer. I just hadn't known that FilesMatch doesn't apply to proxied responses.
I built it in a glass style and mobile dropped to 20fps
My glass-style portfolio fell to 20fps on mobile. I throttled the CPU, measured frame intervals, found background blur, hidden badges and card backdrop-filter, and got 60fps back.
One bundle was 700KB. The criteria I set while splitting it with Vite manualChunks
How I decided what goes into which chunk when splitting a 700KB bundle with Vite manualChunks, and when I use lazy and when I don't.
One of my projects uses Zustand and another uses Redux. Why did the same person pick differently?
How I chose between Redux Toolkit, Zustand and React Query differently depending on the kind of project.
Two exchange WebSockets at once killed my screen. Batching dispatch with requestAnimationFrame
How I batched prices arriving at hundreds of messages per second using a Map queue and rAF, and the alternatives I didn't pick.
My straight-line trip itinerary was off from real roads by 2 hours 28 minutes
An itinerary built on straight-line distance was 148 minutes off real road times. I switched to Kakao directions with a coordinate cache, estimate fallback and days split by area.
Analyzing one link took 17 seconds, so I dropped the request-response approach
One link analysis API took 17.5 seconds, so I turned the request into a job. Progress stages, joining the same link, a rate limit trap, and parallel work to cut the wait.
I ended up reading the captions to pull travel spots out of YouTube videos
To pull travel spots from a YouTube link, I read auto captions with yt-dlp when the description was empty. Why merging AI and rules made results worse, and how I split their roles.
My home page was downloading 19.7MB every time it opened
My blog's home page was pulling 19.7MB, 93% of it images. How I got it down to 782KB with sharp resizing, Lottie and route splitting and font subsetting, all measured.
I went through CSR, SSR, SSG, ISR, streaming and RSC, and what I picked was half an SSR
CSR, SSR, SSG, ISR, streaming SSR, RSC and islands compared with code and trade-offs, and why I still chose a half version that injects meta on the server for the post page only.
I went to upgrade react-query v3 to TanStack Query v5 and useErrorBoundary was gone
From react-query v3 to TanStack Query v5, useErrorBoundary became throwOnError. Covers the single object argument, isPending and the removal of onSuccess, and why each changed.
The TypeScript compiler was rewritten in Go and type checking got 10x faster
tsc was slow because the compiler ran as JavaScript. Why TypeScript 7's Go rewrite is faster, why Go and not Rust, and an honest look at how little it changes for a small project.
JavaScript Date kept leaving me off by a day. How does Temporal in Node 26 solve it?
My couples app's D-day count was off by a day, so I looked at the design problems in JavaScript's Date and how Temporal's PlainDate, ZonedDateTime and Duration in Node 26 fix it.
React 19 says goodbye to forwardRef and puts meta tags in components
What React 19 changed from 18: ref as a prop, Actions and native meta tags, and why my own projects are still on 18.
I took a look inside Vite 8 and the build bundler had been replaced wholesale with Rust
Vite 8 merged esbuild for dev and Rollup for builds into Rust-based Rolldown. The problems two bundlers caused, why builds got several times faster, and why I haven't upgraded yet.
After moving to React, every KakaoTalk link preview looked the same
My React SPA showed the same KakaoTalk preview for every post. How Express inserts per-post meta tags, JSON-LD and a noscript body without SSR, and how canonical burned me later.
I fixed login being lost on refresh with Zustand persist
Admin login was lost on every refresh, and Zustand persist fixed it. What to store, what not to, and the problem in my current code where the token sits in localStorage too.
Why I stored favorites in a Cookie and then moved them to localStorage
Favorites that vanished after a few days led me to compare Cookie and localStorage. Also covers syncing between tabs and how the code actually stores them.
I handled real-time chat, prices and the online user count in one go with Socket.IO
On the crypto site, one Socket.IO connection handles chat, price alerts and the online user count. When to use socket.emit and when io.emit, with the real server code.
I built a real-time monitoring dashboard with SSE instead of polling
I moved a server monitoring screen from 2-second polling to SSE. The Express and EventSource code, and how flushpackets fixed events arriving in bunches behind Apache.
I automatically converted passwords I once stored as SHA-256 to bcrypt at each login
I moved SHA-256 passwords to bcrypt without stopping the service. Covers rehashing at the moment a login succeeds, and when the legacy check can be deleted.
I switched to mysql2 and my ratings suddenly broke. The DECIMAL-as-string trap
After switching to mysql2, my average rating screen threw toFixed is not a function. Why DECIMAL comes back as a string, and how decimalNumbers fixed it.
I replaced code that opened a new DB connection on every request with a connection pool
Opening a new DB connection per request took my site down with Too many connections. The mysql2 pool I switched to, and the createConnection calls I found still left.
A delete that worked locally spat out a 405 on the server
DELETE and PUT worked locally but got a 405 on the server. Why I worked around it with POST, and the real cause I found later: a default ModSecurity rule, with real requests.
I made my server build and deploy on its own with a single git push
A push to GitHub hits a webhook and the server pulls, installs, builds and copies. Also: signature checks, private repo auth and how deploys quietly stopped after I moved a domain.
I'm running 8 services on one server with PM2 and an Apache reverse proxy
I run several services on one server with PM2 and split them by domain with an Apache reverse proxy. Covers VirtualHost config, WebSocket proxying and a wildcard certificate.
I built a server security dashboard by parsing fail2ban and UFW myself
I moved my SSH security checks into one web dashboard. Covers the Node code that reads open ports, failed logins, fail2ban and UFW status, and how I avoided sudo.
How I actually implemented postMessage communication between React and an iframe
How I showed game content in an iframe and exchanged postMessage messages with React: the code on both sides, origin checks, and why it's a stopgap.
From PHP to React, then to Express. A gradual migration when you can't switch all at once (part 2)
Two tangled PHP projects couldn't be rewritten in one go. I attached React screens through iframes, shrank PHP into an API, and moved only what I needed to Express.
Why and how I moved from PHP + HTML to React + Express
My PHP and HTML service grew until state management, component reuse and async UI became a struggle. Why I moved to React and Express, and the practical reasons for Express.
Cleaning up API error handling in a React project with ApiErrorBoundary
I pulled the react-query error handling I'd been copying onto every page into one ApiErrorBoundary. Why useErrorBoundary is needed and how network errors go to GlobalErrorBoundary.