서스펜스 경계는 비동기 작업을 기다리는 하위 트리 대신 fallback을 표시합니다. 경계를 적절히 나누면 느린 위젯 하나가 페이지 전체를 막지 않습니다.
경계가 너무 많으면 화면이 산발적으로 튈 수 있습니다. 사용자가 한 덩어리로 인식하는 영역을 기준으로 묶고 순서를 설계합니다.
언제 쓰나
서버 또는 클라이언트에서 일부 컴포넌트가 데이터·코드를 기다릴 때 사용합니다.
느린 영역에만 대체 화면을 보여주고 준비된 다른 영역은 먼저 렌더링합니다.
<section class="stage"><div class="heading">SUSPENSE <span class="hint">one slow boundary</span></div><div class="cards"><div class="tile ready"><b>PROFILE</b><span>Ready</span></div><div class="tile slow"><b>PICKS</b><span class="fallback">Loading ▬▬</span><span class="content">3 results</span></div><div class="tile ready"><b>FEED</b><span>Ready</span></div></div><div class="caption">Siblings stay visible while one region waits</div></section>.stage{width:min(94vw,780px);height:min(88vh,322px);padding:clamp(12px,3vmin,20px);border:1px solid var(--line);border-radius:16px;background:var(--surface);box-shadow:0 12px 36px #0001;display:flex;flex-direction:column;gap:clamp(7px,2vmin,14px);font:500 clamp(12px,3.5vmin,17px)/1.3 var(--font-sans,system-ui,sans-serif);overflow:hidden}.heading{display:flex;justify-content:space-between;align-items:center;gap:8px;font-weight:750;color:var(--accent)}.hint{color:var(--muted);font-size:clamp(12px,3.2vmin,14px);font-weight:500}.caption{margin-top:auto;color:var(--muted);font-size:clamp(12px,3.2vmin,14px)}.row{display:flex;gap:clamp(5px,1.5vmin,12px);align-items:center}.tile{padding:clamp(6px,1.8vmin,13px);border:1px solid var(--line);border-radius:9px;background:var(--bg);text-align:center;min-width:0}.arrow{color:var(--accent);font-weight:800}.stage [data-on]{transition:background .3s,border-color .3s,opacity .3s,transform .3s}.stage .on{border-color:var(--accent);background:color-mix(in srgb,var(--accent) 14%,var(--surface));color:var(--accent)}.cards{display:flex;flex:1;align-items:stretch;gap:clamp(5px,1.7vmin,14px);min-height:70px}.cards .tile{flex:1;display:flex;flex-direction:column;justify-content:space-between;text-align:left;min-width:0}.cards b{font-size:.8em;overflow:hidden;text-overflow:ellipsis}.cards span{color:var(--accent)}.slow{border-style:dashed}.slow .content{display:none}.fallback{color:var(--muted)!important;animation:breath 1.1s infinite alternate}@keyframes breath{to{opacity:.3}}.stage[data-step="1"] .slow,.stage[data-step="2"] .slow{border-style:solid;border-color:var(--accent)}.stage[data-step="1"] .slow .fallback,.stage[data-step="2"] .slow .fallback{display:none}.stage[data-step="1"] .slow .content,.stage[data-step="2"] .slow .content{display:block}.stage small,.stage button,.stage .cards b,.stage .phase b,.stage .pair b,.stage .tree-mini b{font-size:clamp(12px,3.2vmin,14px)!important}const stage=document.querySelector(".stage");let step=0;function tick(){stage.dataset.step=String(step);step=(step+1)%3}tick();let timer=setInterval(tick,1100);stage.addEventListener("pointerdown",()=>{clearInterval(timer);tick()})서스펜스 경계는 비동기 작업을 기다리는 하위 트리 대신 fallback을 표시합니다. 경계를 적절히 나누면 느린 위젯 하나가 페이지 전체를 막지 않습니다.
경계가 너무 많으면 화면이 산발적으로 튈 수 있습니다. 사용자가 한 덩어리로 인식하는 영역을 기준으로 묶고 순서를 설계합니다.
서버 또는 클라이언트에서 일부 컴포넌트가 데이터·코드를 기다릴 때 사용합니다.