Haliviq

Insights

Today I Learned

Short, practical notes from the Haliviq team: quick lessons from daily engineering and design work. Each one is something we ran into while building, with the reason it happens and what we do about it now. Filter by topic to find notes on React, Next.js, PostgreSQL, Docker, Figma, security and more.

React16 Feb 2026

Wrap heavy list re-renders with useDeferredValue instead of debouncing

Debounce only delays the update, so the expensive render still blocks the page once it fires. useDeferredValue lets React interrupt that render when a new keystroke arrives, so typing stays smooth even on a 5,000-row table. Reach for it when a filter box drives a big list and you do not want to add arbitrary wait times.

Read note
React20 Jan 2026

key={index} silently breaks Framer Motion exit animations

When you reorder or remove items in a list keyed by index, React matches the wrong old node to the new item, so the exit animation plays on the wrong element. Use a stable id from your data as the key. It costs nothing and also fixes odd input-focus and state bugs in the same list.

Read note
React3 Nov 2025

useEffect cleanup running twice in Strict Mode isn't a bug to fix

In development, React 18 mounts, unmounts and re-mounts your component on purpose to surface effects that are not idempotent. If running it twice breaks something, such as a duplicate subscription or a double request, the effect was already unsafe. Fix the cleanup rather than turning Strict Mode off.

Read note
Next.js22 Sept 2025

Server Components re-render on every navigation, not just mount

We assumed the output of a Server Component was cached across route changes. In practice Next.js fetches it again on every soft navigation unless you opt into a caching strategy. If a page feels slow after a client-side link click, check what the server component is fetching and decide explicitly what should be cached and for how long.

Read note
Docker18 Sept 2025

Multi-stage builds cut our image size by 71%

Copying node_modules straight from the build stage dragged in compilers and dev tools that nobody needs at runtime. A clean multi-stage build with COPY --from, copying only the built output and production dependencies, cut the image from 1.2GB to 340MB. Smaller images pull faster and have fewer packages to patch.

Read note
Figma15 Sept 2025

Color contrast checkers lie about gradient text

Most WCAG tools sample a single color, so a gradient heading can pass the check while the lighter end is hard to read for real people. Test the lightest and darkest points of the gradient against the background, or avoid gradients on small and important text altogether.

Read note
PostgreSQL10 Sept 2025

Partial indexes made our soft-delete queries 40x faster

We were indexing 2M soft-deleted rows that the app never queries. A partial index with WHERE deleted_at IS NULL covers only live rows, so it is smaller and fits in memory, and it cut query time from 800ms to 20ms. Any table where most queries hit a small, well-defined slice of rows is a good candidate.

Read note
CLI29 Aug 2025

Debouncing search input isn't enough — you need request cancellation too

Even with debounce, a slow response can arrive after a newer one and overwrite it with stale results. Creating an AbortController per request and aborting the previous one fixed the old results flashing in for fast typists. Cancelling also saves the server from finishing work nobody will see.

Read note
macOS25 Aug 2025

Quick Look caches a stale thumbnail after a file gets overwritten

Run qlmanage -r to reset the cache. Without it, a re-exported PNG can keep showing yesterday's preview in Finder for hours, which is confusing when you are checking design exports or handing off assets.

Read note
Security19 Aug 2025

Rotating a leaked API key is not the same as revoking its sessions

Signed sessions that were issued with the old key stayed valid for 24 hours after we rotated it, so an attacker could still use them. Our incident checklist now always revokes active sessions and tokens as well as replacing the key, and then checks the logs for use of the old one.

Read note
Next.js4 Aug 2025

revalidatePath doesn't bust the client-side router cache

The server data was fresh, but users still saw stale content for up to 30 seconds because the browser keeps its own router cache that revalidatePath does not touch. When freshness matters, such as after a form submit, trigger a router refresh on the client as well, and test the flow by clicking around rather than only reloading.

Read note
TypeScript12 Jul 2025

satisfies keeps literal types AND checks the shape

Annotating a variable with a type widens it, so you lose the exact literal values. The satisfies operator validates the object against the type but keeps the narrow inference, which is handy for config objects and route maps where you want both autocomplete on the real keys and a safety net on the shape.

Read note
PostgreSQL30 Jun 2025

EXPLAIN ANALYZE lies about buffer cache on the first run

The first execution pulls cold pages from disk, so the timing looks worse than what users will normally see. Always run it twice and read the second pass, and add BUFFERS to see how much came from cache. Comparing a cold run with a warm one is a common way to misjudge whether an index helped.

Read note
Design20 Jun 2025

Thai line-height needs ~1.7, not the 1.5 that works for Latin text

Thai script stacks vowels and tone marks above and below the baseline, so a Latin-friendly line-height of 1.5 clips them or makes them touch the next line. Bumping body text to about 1.7 fixed clipped vowels across the whole site. Test with words that carry stacked marks, and check headings separately since they usually need less.

Read note
TypeScript28 May 2025

Optional chaining hides typos in property names from the compiler

If the type is loose or any, user?.emial quietly returns undefined instead of raising an error, and the typo ships to production unnoticed. Keep the object properly typed so the compiler can flag the misspelling, and treat a lot of optional chaining as a hint that the data model is not well defined.

Read note
Docker2 Apr 2025

.dockerignore doesn't apply to COPY --from another stage

We kept finding .env in the final image because .dockerignore only filters the build context sent to Docker, not files copied between stages. Be explicit about what you copy in later stages, never copy the whole directory, and inspect the final image to confirm no secrets are inside.

Read note
Figma11 Mar 2025

Auto layout 'hug contents' breaks silently on empty strings

A frame set to hug its contents collapses to zero width when the bound text is empty, and the layers panel gives no warning. Give components a minimum width, or design an explicit empty state, so a missing value in real data does not make an element disappear.

Read note
CLI2 Feb 2025

fzf + ripgrep beats grep -r for anything in a monorepo

rg --files | fzf gives fuzzy file jumping, and wrapping rg in fzf with a preview window gives live content search. In a big monorepo this brought our search time down to almost nothing, mostly because ripgrep respects .gitignore and skips build folders by default.

Read note

Have a project in mind?

We'd love to hear what you're building.

These notes come from our daily design and engineering work in Bangkok. If you are planning a website, app or AI tool and want a team that pays attention to details like these, tell us about it.