블로그

React use()와 Suspense: 워터폴 없는 데이터 스트리밍

대시보드에 6개의 카드, 6개의 스피너, 그것들은 함께 나타나지 않습니다 — 각 카드가 위의 것이 완료된 후에야 로딩을 시작했기 때문에 도미노처럼 하나씩 나타납니다. 이것이 워터폴이고, 총 대기 시간은 가장 느린 요청이 아니라 모든 것의 합계입니다. use()와 Suspense는 그 체인을 끊습니다 — 하지만 흥미로운 것은 내부 메커니즘입니다: 일시 중단된 컴포넌트가 실제로 무엇을 던지는지, 그리고 잘못된 순서로 도착한 HTML이 어떻게 올바른 위치에 착지하는지.

Core Web Vitals: INP가 실제로 측정하는 것, 그리고 scheduler.yield()가 메인 스레드를 구하는 법

마이크로 인터랙션에 관한 글에서 저는 transform과 opacity가 컴포지터 스레드에서 애니메이션되므로, 아무리 무거운 페이지라도 버튼 애니메이션이 끊겨서는 안 된다고 썼습니다. 이는 여전히 사실입니다 – 다만 그 글이 침묵했던 한 가지 단서가 있습니다. 메인 스레드가 긴 JS 작업으로 막혀 있다면, 애니메이션은 시작할 기회조차 얻지 못할 수 있습니다. 클릭 핸들러 자체가 대기열에서 기다리고 있기 때문입니다. INP는 정확히 이 틈을 측정하는 지표이며, scheduler.yield()는 그 틈을 실제로 메울 수 있는 몇 안 되는 방법 중 하나입니다.

content-visibility: 리스트 가상화 없이 화면 밖 렌더링을 건너뛰는 법

댓글 천 개짜리 목록이 느리게 렌더링되어 react-window를 꺼내 듭니다 – 그런데 부드러운 스크롤을 얻는 대가로 Ctrl+F, 페이지 인쇄, 그리고 스크린 리더 탐색 일부를 잃게 됩니다. 화면 밖 요소들이 DOM에서 물리적으로 사라져버리기 때문입니다. content-visibility는 같은 문제를 다른 방식으로 풉니다 – 요소는 DOM에 그대로 남아 있고, 브라우저는 화면 근처에 오기 전까지 비용이 큰 레이아웃과 페인트 작업만 건너뜁니다. 그리고 놀랍게도, 그 안의 텍스트를 검색할 때는 일시적으로 다시 드러내 줄 수도 있습니다.

Popover API와 네이티브 <dialog>: 처음부터 직접 만드는 모달의 종말

접근성 글에서 다룬, display:none으로 감췄지만 접근성 트리에는 여전히 남아 있던 모달은 div로 처음부터 직접 만든 모달에서 비롯된 실수의 한 예였습니다. 진짜 질문은 애초에 왜 그것을 처음부터 만들어야 했는가입니다. 네이티브 <dialog>와 popover 속성이 공짜로 제공하는 것 – 톱 레이어, 포커스 트랩, light dismiss – 이 무엇인지, 그리고 이 두 메커니즘이 서로 헷갈리면 곧바로 접근성이 떨어지는 인터페이스로 이어질 만큼 다른 지점이 어디인지 살펴봅니다.

React의 Optimistic UI: 의도적으로 거짓말하는 인터페이스

'좋아요' 버튼은 서버가 응답하기도 전에 즉시 반응하며, 하트가 채워지고 카운터가 올라갑니다. 이는 마이크로 인터랙션 글에서 다룬 속도의 착시가 아니라, 그보다 한 걸음 더 나아간 것입니다. 인터페이스가 아직 존재하지 않는 상태를 보여주며, 그것이 곧 실현될 것이라고 가정하는 것입니다. 이를 안전하게 만드는 법 – 가정이 틀렸을 때 그 거짓말에서 실제로 되돌아 나오는 방법 – 그리고 수동 setState와 catch가 useOptimistic이 제공하는 것보다 왜 열등한 버전인지 살펴봅니다.

Next.js Server Actions: JavaScript가 로드되기도 전에 작동하는 폼

'use server'는 API 라우트로의 fetch를 위한 문법적 설탕처럼 보입니다 – 그리고 바로 그 이유로 대부분의 구현이 이를 그냥 함수 호출처럼 다룹니다. 이는 구체적인 결과를 초래하는 실수입니다. 'use server'가 붙은 모든 함수는 네트워크상의 공개 엔드포인트가 되며, 이 메커니즘 위에 지어진 폼은 JavaScript가 로드되기도 전에 작동합니다. 이 지시어 아래에서 실제로 무슨 일이 일어나는지, 그리고 문서만 봐서는 한눈에 보이지 않는 함정이 어디에 있는지 살펴봅니다.