블로그

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

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

스크롤 애니메이션: IntersectionObserver 대신 animation-timeline

JavaScript 패럴랙스가 항상 조금 이상해 보이는 이유가 있습니다 — 약간 분리된 느낌, 레이어가 페이지보다 반 프레임 뒤에서 떠다니는 것 같은. 이것은 나쁜 코드가 아닙니다. 브라우저가 이미 스크롤을 그린 후에 스크롤 이벤트가 JavaScript에 도달하기 때문입니다. 브라우저는 이것을 한번 해결했습니다 — 고정 헤더를 position: sticky로 바꿀 때. Scroll-driven animations는 동일한 접근법을, 모든 스크롤 기반 효과에 한번에 적용한 것입니다.

View Transitions와 Next.js: 라이브러리 없이 페이지 애니메이션

10년 동안 썸네일을 히어로 이미지로 애니메이션하는 방법은 하나뿐이었습니다: 전후 위치를 측정하고, transform으로 차이를 속이고, 재생하세요. FLIP. 모든 애니메이션 라이브러리가 이것을 하고, 모두 최악의 순간에 메인 스레드의 JavaScript라는 대가를 치릅니다. View Transitions API는 그 트릭 전체를 브라우저로 옮깁니다 — React 19.2와 Next.js 16부터 전체 계약은 name이라는 하나의 prop으로만 구성됩니다.

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 – 이 무엇인지, 그리고 이 두 메커니즘이 서로 헷갈리면 곧바로 접근성이 떨어지는 인터페이스로 이어질 만큼 다른 지점이 어디인지 살펴봅니다.