Suspense boundary

서스펜스 경계

A slow region shows a fallback while ready siblings can render first.

···
html
<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>
css
.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}
js
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()})

A Suspense boundary displays a fallback while its child tree waits for asynchronous work. Splitting boundaries lets ready siblings appear without waiting for one slow widget.

Too many boundaries can make the screen pop in unpredictably. Group regions that users perceive as one unit and plan reveal order.

When to use

Use it when part of a server or client view waits for data or code.

Open as page ↗