What This Post Covers
A web page serves two kinds of readers at once. One is people (visitors who look at the screen and scroll); the other is machines (search crawlers, bots that expand links into previews, analytics tools). Today’s work was about making both see the same facts while keeping the code from getting messy.
Four things, concretely. ① Open the home so logged-out visitors and search bots also get something to read; ② keep public pages from breaking while login status is still being checked; ③ build one channel that sends analytics events to several tools at once; and ④ turn off page-transition animation when it would fight accessibility and scrolling.
No specific product or company story — only general web concepts from that day.
Day Summary
| Task | Goal | What I Did | Outcome |
|---|---|---|---|
| Public home & SEO | Give search bots and visitors content to read on home | Pull screen text and bot-only hidden info from one source | Prevented thin pages / false info |
| Login-check wait | Keep public pages from breaking while login is checking | Call protected API only when "checked + actually logged in" | Stable anonymous / search paths |
| Analytics channel | Send every event to several analytics tools reliably | Screen calls one channel; it copies to many tools behind | Removed per-tool call mistakes |
| Page transitions | Smooth transitions + keep accessibility/scroll intact | If "reduce motion" is on, don’t enable the transition at all | Prevented scroll conflicts / test flake |
1. Don’t Sneak Info Into Bot-Only Markup When It’s Not on Screen
A search engine reads not only the on-screen text people see, but also the machine-facing summary info hidden in the page. If that hidden info differs from the visible text, it’s treated as a trick and hurts you. Today I learned how to keep the two identical.
Background
- Structured data — ① summary info added so search engines quickly understand the page; invisible to human eyes. ② Without it, rich results like star ratings or FAQs don’t appear in search. ③ Usually added via a script called JSON-LD (this summary written in JSON).
- Visible content — i.e., the text people actually see on screen.
- Thin content — i.e., a page with almost no real substance, so search engines judge it low quality. A login wall that empties the body produces this.
- Sitemap — i.e., the list of addresses you hand search bots saying "our site has these URLs."
Situation. With many pages requiring login, the home easily looked empty to logged-out visitors and search bots. Conversely, stuffing FAQ summary info that isn’t on screen becomes a "trick."
Takeaway. Keep the hidden summary info identical to the visible text. Keep the FAQ list as one piece of data that both the screen and the summary share, so removing an FAQ from the screen automatically removes it from the summary too. Even if you blur the login wall, put the real title, description, and FAQ text on top to avoid a thin page. Put only internal addresses that actually open (return a normal response) into the sitemap, and don’t force in pages that navigate away to external sites.
→ Structured Data and Visible Content Parity
2. Don’t Call Protected APIs Before the Login Check Finishes
When fetching data that requires login, confusing "login is still being checked" with "checked and found logged out" makes public pages wrongly bounce to the login screen. I learned how to tell these two apart.
Background
- Protected API — i.e., a server address only a logged-in person can call. Called without login info (a cookie), the server returns a 401 (unauthorized) error.
- Session loading — i.e., the brief moment the browser asks the server "is this person logged in?" and waits for the answer. During it, login status is still unknown.
enabled— a switch in data-fetching libraries (e.g., React Query) that means "only send the request when this condition is true." i.e., ifenabledis false, the API isn’t called at all.- Mock server — i.e., a test server that returns pre-set fake responses instead of the real thing.
Situation. A visit without cookies called a protected API and got a 401, and the global rule "on 401, send to login" fired, bouncing even the public home to the login screen. Tests missed it because the fake server always returned success.
Takeaway. "No user info (user === null)" may mean not checked yet, not logged out. So the request switch must check two conditions together, like authResolved && isAuthenticated (the login check has finished + it’s an actually logged-in user). Public pages turn off protected APIs and use static/fake data only. Verify in a real browser with cookies cleared.
→ Auth-Resolution-Gated Data Fetching
3. Gather Analytics Events Into One Channel and Send Them Everywhere
When sending a record like "user clicked the button" to several analytics tools, having the screen code call each tool directly leads to accidentally sending to only one, or a tool’s error blowing up the checkout code. I learned the pattern of putting one channel in the middle.
Background
- Analytics event — i.e., a record of user behavior like "signup complete" or "checkout button click." These are collected into usage stats.
- Vendor SDK — i.e., a bundle of code from an analytics company (Google, etc.) that sends events to that company’s server.
- Facade pattern — ① a design that puts one simple counter in front even though many complex things sit behind it. ② It lets the screen code not care which tool is used. ③ Like talking only to one teller instead of many bank departments.
- Fan-out — i.e., taking one incoming thing and copying it to several destinations automatically.
Situation. Calling analytics tool code directly at each screen meant some screens sent records to only one tool (skewing stats), and if a tool errored, that exception could shake even the checkout/login flow.
Takeaway. The screen knows only three counter functions: track (record an event), identify (say who it is), and reset (clear on logout). Behind them, it copies and distributes to the actual several analytics tools. Each tool is turned on/off independently via config, and a failed tool call is swallowed quietly so the core feature (checkout, etc.) doesn’t stop. Helpers that are exported but used nowhere shouldn’t be revived during a move or cleanup.
→ Client Analytics Event Fan-Out Facade
4. If "Reduce Motion" Is On, Turn Off the Page Transition in the App
I wanted a smooth cross-fade when navigating between pages. But for users who’ve asked to turn off animation (e.g., due to dizziness), that effect is actually harmful. Just shrinking the CSS duration to 0 wasn’t enough — the transition feature itself had to be turned off.
Background
prefers-reduced-motion— ① a setting that tells you whether the user has turned on "minimize animation" in their OS/browser. ② an accessibility feature for people sensitive to dizziness/motion sickness. ③ when on, you should remove flashy motion.- JS spring transition vs CSS transition — i.e., if JavaScript computes the animation (a spring), shrinking the CSS time (
--duration) to 0 still leaves the JS side alive, so it doesn’t turn off. matchMedia+useSyncExternalStore— i.e., a way to detect that setting changing in real time (matchMedia) and safely reflect it into the React UI (useSyncExternalStore).- Provider — i.e., a wrapper component that supplies a feature to all the UI beneath it. Here it acts as the switch that turns on the "page transition" feature.
- Virtual list — i.e., a technique that, in a long list, actually renders only the few items visible on screen to save performance. Its scroll-position handling is sensitive.
Situation. Adding a global transition, the animation was driven by JavaScript so shrinking only the CSS time to 0 didn’t turn it off, and it risked conflicting with long-list scroll position or screen-comparison tests (VR).
Takeaway. Subscribe in real time to the "minimize animation" setting, and when it’s on, don’t even turn on the transition-feature provider (don’t mount it). And make the transition handle only visuals and not touch the scroll position, so it stays separate from the list’s own authority to restore scroll.
→ prefers-reduced-motion and JS Page Transition Gate