Rules Hub
Coding Rules Library
Rule priority, scope & exceptions
Use this to align rules with the senior-level structure (P0/P1/P2, scope, exceptions/tradeoffs).
Render SSR-safe defaults for client measurement logic
When UI depends on client-only measurements, render a stable SSR fallback and enhance on the client.
Bad example
| 1 | import { useLayoutEffect, useRef, useState } from "react"; |
| 2 |
|
| 3 | export function ResponsiveTabs({ items }: { items: string[] }) { |
| 4 | const ref = useRef<HTMLDivElement | null>(null); |
| 5 | const [visibleCount, setVisibleCount] = useState(0); |
| 6 |
|
| 7 | // On SSR, this doesn't run. First paint may be wrong and then jump. |
| 8 | useLayoutEffect(function measure() { |
| 9 | const el = ref.current; |
| 10 | if (!el) return; |
| 11 |
|
| 12 | const width = el.getBoundingClientRect().width; |
| 13 | setVisibleCount(width > 600 ? items.length : 3); |
| 14 | }, [items.length]); |
| 15 |
|
| 16 | return ( |
| 17 | <div ref={ref}> |
| 18 | {items.slice(0, visibleCount).map((t) => ( |
| 19 | <button key={t}>{t}</button> |
| 20 | ))} |
| 21 | </div> |
| 22 | ); |
| 23 | } |
Explanation (EN)
The initial SSR output cannot know the container width, so the first render may show the wrong number of tabs and then visibly jump after hydration/measurement.
Objašnjenje (HR)
Pocetni SSR output ne moze znati sirinu kontenjera, pa prvi render moze prikazati krivi broj tabova i onda vidljivo skociti nakon hydrationa/mjerenja.
Good example
| 1 | import { useEffect, useRef, useState } from "react"; |
| 2 |
|
| 3 | export function ResponsiveTabs({ items }: { items: string[] }) { |
| 4 | const ref = useRef<HTMLDivElement | null>(null); |
| 5 |
|
| 6 | // SSR-safe default that is stable. |
| 7 | const [visibleCount, setVisibleCount] = useState(() => Math.min(3, items.length)); |
| 8 |
|
| 9 | useEffect(function measure() { |
| 10 | const el = ref.current; |
| 11 | if (!el) return; |
| 12 |
|
| 13 | const width = el.getBoundingClientRect().width; |
| 14 | const next = width > 600 ? items.length : Math.min(3, items.length); |
| 15 | setVisibleCount(next); |
| 16 | }, [items.length]); |
| 17 |
|
| 18 | return ( |
| 19 | <div ref={ref}> |
| 20 | {items.slice(0, visibleCount).map((t) => ( |
| 21 | <button key={t}>{t}</button> |
| 22 | ))} |
| 23 | </div> |
| 24 | ); |
| 25 | } |
Explanation (EN)
Render a stable SSR default (e.g., 3 tabs) and enhance on the client after mount. This avoids hydration surprises and reduces layout jumping.
Objašnjenje (HR)
Renderaj stabilan SSR default (npr. 3 taba) i poboljsaj na clientu nakon mounta. Time se izbjegnu hydration iznenadenja i smanji layout skakanje.
Notes (EN)
For critical layouts, consider CSS solutions (container queries, overflow) before JavaScript measurement. If you must measure, keep the SSR default visually acceptable.
Bilješke (HR)
Za kriticne layoute, razmisli o CSS rjesenjima (container queries, overflow) prije JS mjerenja. Ako moras mjeriti, SSR default neka bude vizualno prihvatljiv.
Exceptions / Tradeoffs (EN)
If the entire route is client-only and not SSR rendered, SSR defaults are less critical (but still consider first paint UX).
Iznimke / Tradeoffi (HR)
Ako je cijeli route client-only i nije SSR renderiran, SSR defaulti su manje kriticni (ali i dalje razmisli o UX-u prvog painta).