I Refactored This Blog for UX, Not Vanity
From server-first rendering to no-JS fallback and cross-browser checks.
Feb 14, 2026
I keep telling teams to optimize for user flow, then realized I was not fully doing it on my own blog.
It was decent. It was readable. It worked for me. But it was still carrying the classic engineering smell: features that were useful in isolation, yet expensive on first load and fragile across edge cases.
So I took one full pass and treated this site like a real product with explicit UX constraints:
- first render should stay mostly server-first
- core reading should work even when JavaScript is disabled
- browser compatibility should be enforced by tests, not confidence
That is what this post is about: exactly what changed, why it mattered, and what actually worked.
The UX Tax I Was Quietly Charging
Nothing was catastrophically broken, but there were hidden costs:
- too much client-only behavior mounted globally
- homepage feed behavior depended on JavaScript for progressive loading
- analytics and auxiliary listeners were eager
- rendering paths were not explicitly validated in Firefox/WebKit/no-JS mode
All of this adds up to a subtle kind of friction. Not enough to trigger a bug report, but enough to reduce perceived speed and resilience.
The Core Decision: Stay Server-First, Enhance Later
I am already on Next.js App Router, so React Server Components are the default. But default does not mean free. You still need to decide where client boundaries live.
The change in mindset was:
- keep the critical reading path as plain HTML from the server
- load interactivity only where it creates clear user value
- let no-JS mode be a first-class baseline, not an afterthought
What Changed in the Code
1) Homepage feed is now server-rendered end to end
I removed the client-side infinite feed dependency from the home page and render the full post list server-side.
Why this helped:
- works out of the box without JavaScript
- no hydration required for basic discovery/navigation
- less client orchestration on first interaction
2) Heavy client features moved behind explicit boundaries
Interactive components that are genuinely optional were pushed behind client-only dynamic imports, for example:
- inline AI helper on article pages
- article analytics hooks
- theme toggle UI
The reading experience no longer waits for optional behavior.
3) No-JS behavior became intentional
I added explicit JS-only handling for non-essential controls and verified that the core journey still works:
- browse homepage
- open post
- read content
- navigate using footer/header links
This is progressive enhancement in practice, not in slideware.
4) Analytics was demoted from critical path
Tracking scripts were moved to lazy loading, and some eager global listeners were removed.
Trade-off:
- you might lose a small amount of early-session telemetry
- readers get less main-thread work during initial interaction
I will usually take that trade.
5) Mermaid rendering is loaded on demand
A bundling issue around Mermaid was fixed by dynamic loading at render time in the client component.
This solved two problems:
- build reliability improved for content pages
- non-diagram pages stop paying for diagram code upfront
Browser Compatibility Is Now a System, Not a Promise
After code cleanup, I set up a compatibility workflow with Playwright:
- Chromium
- Firefox
- WebKit (Safari engine)
- Chromium with JavaScript disabled
I also added a documented browser support policy and no-JS baseline expectations. Now the contract is explicit and testable.
This part matters more than the tool itself. Most UX regressions are not caused by ignorance, they are caused by missing feedback loops.
What Worked (and What I Learned Again)
The biggest lesson did not change:
Speed is less about one clever optimization and more about architectural honesty.
If a feature is optional, load it optionally. If a path is critical, make it server-first. If you claim compatibility, test compatibility.
Also, not every improvement will show up as dramatic bundle-size wins. Some of the biggest gains here are in resilience:
- better no-JS behavior
- safer browser coverage
- fewer fragile assumptions in the rendering path
That is still UX work.
If You Want to Do This on Your Site
Here is the short checklist I would repeat:
- identify which screens must work as static HTML only
- move non-critical client logic behind explicit boundaries
- add a no-JS browser project in your E2E suite
- test Firefox and WebKit every PR, not just Chromium
- treat tracking and convenience UI as secondary to reading flow
This refactor was not glamorous. But it made the blog feel more solid, more predictable, and more respectful of reader context.