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.
In my exchange WebSocket post I said I batched rendering with rAF, but there were no numbers at all. Replaying recorded real prices, there was almost no difference normally, and it only split into 7.6fps and 37.6fps when a slow phone met a busy market.
My crypto mock trading site had no tests at all, so I started with liquidation math and the reconnecting socket using Vitest. 1 of 26 tests failed, and it was a real bug: with several cross positions, the same cash was counted as lost more than once.
I switched travel times to real road times, but stay time was still 90 minutes for any attraction. I reset it per place using tourism category codes and name rules, then recalculated 27 real itineraries with the old and new rules.
On October 7, 9 services stopped responding together for 80 minutes. Not a single down mail went out, and only 9 recovery mails arrived. I changed the status page to group failures in the same time window into a common outage and show them separately.
I had the screen wait for the visitor count API, so the home's post list request was stuck in line behind it. I made it stop waiting, and with a simulated mobile connection the home went from 3.98 to 3.39 seconds.
Health check records piled up every minute with no code to delete them, so the status page query scanned the whole table every time. With a daily summary table and deleting only success rows after 30 days, the query went from 0.68s to 0.012s.
After adding code block highlighting I checked the home page requests and found the 71KB highlight.js used only on post pages. The cause was Rollup's CommonJS helper, and for the same reason lodash had been loading on the home page for over a year.
Because of wildcard DNS and the Apache default vhost, nonexistent subdomains landed on my blog and rendered a blank page. How I settled file order, the wildcard cert, status codes and noindex while replacing it with a landing page.
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.
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 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.
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.
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.
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.
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.
How I chose between Redux Toolkit, Zustand and React Query differently depending on the kind of project.
How I batched prices arriving at hundreds of messages per second using a Map queue and rAF, and the alternatives I didn't pick.
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.
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.
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 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.
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.
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.
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.
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.
What React 19 changed from 18: ref as a prop, Actions and native meta tags, and why my own projects are still on 18.
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.
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.
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.
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.
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 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 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.
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.
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.
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.
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 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 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 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.
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.
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.
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.