<Suspense> lets you display a fallback until its children have finished loading. <Suspense>를 사용하면 자식이 로딩을 완료할 때까지 폴백을 표시할 수 있습니다.

<Suspense fallback={<Loading />}>
<SomeComponent />
</Suspense>

Reference참조

<Suspense>

Props

  • children: The actual UI you intend to render. If children suspends while rendering, the Suspense boundary will switch to rendering fallback. children: 렌더링하려는 실제 UI입니다. 렌더링하는 동안 children이 일시 중단되면 Suspense 경계가 fallback 렌더링으로 전환됩니다.

  • fallback: An alternate UI to render in place of the actual UI if it has not finished loading. Any valid React node is accepted, though in practice, a fallback is a lightweight placeholder view, such as a loading spinner or skeleton. Suspense will automatically switch to fallback when children suspends, and back to children when the data is ready. If fallback suspends while rendering, it will activate the closest parent Suspense boundary. fallback: 로딩이 완료되지 않은 경우에 실제 UI 대신 렌더링할 대체 UI입니다. 유효한 어떤 React 노드든 괜찮지만 주로 로딩 스피너나 스켈레톤과 같은 가벼운 플레이스홀더 뷰를 사용합니다. Suspense는 children이 일시 중단되면 자동으로 fallback으로 전환되고, 데이터가 준비되면 다시 children으로 전환됩니다. 렌더링 중에 fallback이 일시 중단되면 가장 가까운 상위 Suspense 경계가 활성화됩니다.

Caveats주의사항

  • React does not preserve any state for renders that got suspended before they were able to mount for the first time. When the component has loaded, React will retry rendering the suspended tree from scratch. React는 처음 마운트하기 전에 일시 중단된 렌더링의 state를 보존하지 않습니다. 컴포넌트가 로드되면 React는 일시 중단된 트리의 렌더링을 처음부터 다시 시도합니다.

  • If Suspense was displaying content for the tree, but then it suspended again, the fallback will be shown again unless the update causing it was caused by startTransition or useDeferredValue. Suspense가 트리에 대한 콘텐츠를 표시하고 있다가 다시 일시 중단된 경우, 그 원인이 된 업데이트가 startTransition이나 useDeferredValue로 인한 것이 아니라면 fallback이 다시 표시됩니다.

  • If React needs to hide the already visible content because it suspended again, it will clean up layout Effects in the content tree. When the content is ready to be shown again, React will fire the layout Effects again. This ensures that Effects measuring the DOM layout don’t try to do this while the content is hidden. React가 다시 일시 중단되어 이미 표시된 콘텐츠를 숨겨야 하는 경우, 콘텐츠 트리에서 layout Effect를 클린업 합니다. 콘텐츠가 다시 표시될 준비가 되면 React는 layout Effect를 다시 실행합니다. 이를 통해 콘텐츠가 숨겨져 있는 동안에는 DOM 레이아웃을 측정하는 Effect가 해당 작업을 시도하지 않도록 합니다.

  • React includes under-the-hood optimizations like Streaming Server Rendering and Selective Hydration that are integrated with Suspense. Read an architectural overview and watch a technical talk to learn more. React에는 Suspense와 통합된 스트리밍 서버 렌더링선택적 Hydration과 같은 내부 최적화가 포함되어 있습니다. 아키텍처 개요기술 강연에서 자세히 알아보세요.


Usage사용법

Displaying a fallback while content is loading 콘텐츠를 로딩하는 동안 폴백 표시하기

You can wrap any part of your application with a Suspense boundary: 애플리케이션의 어떤 부분이든 Suspense 경계로 감쌀 수 있습니다:

<Suspense fallback={<Loading />}>
<Albums />
</Suspense>

React will display your loading fallback until all the code and data needed by the children has been loaded. React는 자식에게 필요한 모든 코드와 데이터가 로드될 때까지 로딩 폴백을 표시합니다.

In the example below, the Albums component suspends while fetching the list of albums. Until it’s ready to render, React switches the closest Suspense boundary above to show the fallback—your Loading component. Then, when the data loads, React hides the Loading fallback and renders the Albums component with data. 아래 예시에서는 앨범 목록을 가져오는 동안 Albums 컴포넌트가 일시 중단됩니다. 렌더링할 준비가 될 때까지 React는 가장 가까운 상위 Suspense 경계를 폴백인 Loading 컴포넌트로 전환하여 표시합니다. 이후 데이터가 로드되면 React는 Loading 폴백을 숨기고 데이터와 함께 Albums 컴포넌트를 렌더링합니다.

import { Suspense } from 'react';
import Albums from './Albums.js';

export default function ArtistPage({ artist }) {
  return (
    <>
      <h1>{artist.name}</h1>
      <Suspense fallback={<Loading />}>
        <Albums artistId={artist.id} />
      </Suspense>
    </>
  );
}

function Loading() {
  return <h2>🌀 Loading...</h2>;
}

Note

Only Suspense-enabled data sources will activate the Suspense component. They include: 오직 Suspense를 도입한 데이터 소스에서만 Suspense 컴포넌트를 활성화할 수 있습니다. 여기에는 다음이 포함됩니다:

  • Data fetching with Suspense-enabled frameworks like Relay and Next.js RelayNext.js와 같은 Suspense 도입 프레임워크를 사용한 데이터 페칭

  • Lazy-loading component code with lazy lazy를 사용한 지연 로딩 컴포넌트 코드

  • Reading the value of a Promise with use

Suspense does not detect when data is fetched inside an Effect or event handler. Suspense는 Effect나 이벤트 핸들러 내부에서 페칭하는 경우를 감지하지 않습니다.

The exact way you would load data in the Albums component above depends on your framework. If you use a Suspense-enabled framework, you’ll find the details in its data fetching documentation. 위의 Albums 컴포넌트에서 데이터를 로드하는 정확한 방법은 프레임워크에 따라 다릅니다. Suspense를 도입한 프레임워크를 사용하는 경우, 해당 프레임워크의 데이터 페칭 문서에서 자세한 내용을 확인할 수 있을 것입니다.

Suspense-enabled data fetching without the use of an opinionated framework is not yet supported. The requirements for implementing a Suspense-enabled data source are unstable and undocumented. An official API for integrating data sources with Suspense will be released in a future version of React. 잘 알려진 프레임워크를 사용하지 않고 데이터 페칭에 Suspense를 도입하는 방법은 아직 지원되지 않습니다. Suspense를 도입한 데이터 소스를 구현하기 위한 요구 사항은 불안정하고 문서화되지 않았습니다. 데이터 소스를 Suspense와 통합하기 위한 React 공식 API는 미래에 출시할 계획입니다.


Revealing content together at once콘텐츠를 한 번에 드러내기

By default, the whole tree inside Suspense is treated as a single unit. For example, even if only one of these components suspends waiting for some data, all of them together will be replaced by the loading indicator: 기본적으로 Suspense 내부의 전체 트리는 단일 단위로 취급됩니다. 예를 들어, 다음 컴포넌트 중 하나만 데이터 대기를 위해 일시 중단하더라도 모든 컴포넌트가 함께 로딩 표시기로 대체됩니다:

<Suspense fallback={<Loading />}>
<Biography />
<Panel>
<Albums />
</Panel>
</Suspense>

Then, after all of them are ready to be displayed, they will all appear together at once. 이후 모든 항목이 표시될 준비가 되면, 이들 모두가 한꺼번에 표시됩니다.

In the example below, both Biography and Albums fetch some data. However, because they are grouped under a single Suspense boundary, these components always “pop in” together at the same time. 아래 예제에서는 BiographyAlbums 모두 데이터를 가져옵니다. 그러나 이 두 컴포넌트는 단일 Suspense 경계 아래에 그룹화되어 있기 때문에 항상 동시에 함께 “등장”합니다.

import { Suspense } from 'react';
import Albums from './Albums.js';
import Biography from './Biography.js';
import Panel from './Panel.js';

export default function ArtistPage({ artist }) {
  return (
    <>
      <h1>{artist.name}</h1>
      <Suspense fallback={<Loading />}>
        <Biography artistId={artist.id} />
        <Panel>
          <Albums artistId={artist.id} />
        </Panel>
      </Suspense>
    </>
  );
}

function Loading() {
  return <h2>🌀 Loading...</h2>;
}

Components that load data don’t have to be direct children of the Suspense boundary. For example, you can move Biography and Albums into a new Details component. This doesn’t change the behavior. Biography and Albums share the same closest parent Suspense boundary, so their reveal is coordinated together. 데이터를 로드하는 컴포넌트가 Suspense 경계의 직접적인 자식일 필요는 없습니다. 예를 들어, BiographyAlbums를 새 Details 컴포넌트로 이동할 수도 있습니다. 이렇게 해도 동작은 달라지지 않습니다. BiographyAlbums는 가장 가까운 상위 Suspense 경계를 공유하므로 표시 여부가 함께 조정됩니다.

<Suspense fallback={<Loading />}>
<Details artistId={artist.id} />
</Suspense>

function Details({ artistId }) {
return (
<>
<Biography artistId={artistId} />
<Panel>
<Albums artistId={artistId} />
</Panel>
</>
);
}

Revealing nested content as it loads중첩된 콘텐츠가 로드될 때 표시하기

When a component suspends, the closest parent Suspense component shows the fallback. This lets you nest multiple Suspense components to create a loading sequence. Each Suspense boundary’s fallback will be filled in as the next level of content becomes available. For example, you can give the album list its own fallback: 컴포넌트가 일시 중단되면 가장 가까운 상위 Suspense 컴포넌트가 폴백을 표시합니다. 이를 통해 여러 Suspense 컴포넌트를 중첩하여 로딩 시퀀스를 만들 수 있습니다. 각 Suspense 경계의 폴백은 다음 레벨의 콘텐츠를 사용할 수 있게 되면 채워집니다. 예를 들어, 앨범 목록에 자체 폴백을 지정할 수 있습니다:

<Suspense fallback={<BigSpinner />}>
<Biography />
<Suspense fallback={<AlbumsGlimmer />}>
<Panel>
<Albums />
</Panel>
</Suspense>
</Suspense>

With this change, displaying the Biography doesn’t need to “wait” for the Albums to load. 이 변경으로, Biography를 표시하기 위해 Albums가 로드될 때까지 “기다릴” 필요가 없어졌습니다.

The sequence will be: 순서는 다음과 같습니다:

  1. If Biography hasn’t loaded yet, BigSpinner is shown in place of the entire content area. Biography가 아직 로드되지 않은 경우 전체 콘텐츠 영역 대신 BigSpinner가 표시됩니다.

  2. Once Biography finishes loading, BigSpinner is replaced by the content. Biography로드가 완료되면 BigSpinner가 콘텐츠로 대체됩니다.

  3. If Albums hasn’t loaded yet, AlbumsGlimmer is shown in place of Albums and its parent Panel. Albums가 아직 로드되지 않은 경우 Albums 및 그 부모 Panel 대신 AlbumsGlimmer가 표시됩니다.

  4. Finally, once Albums finishes loading, it replaces AlbumsGlimmer. 마지막으로 Albums 로딩이 완료되면 AlbumsAlbumsGlimmer를 대체합니다.

import { Suspense } from 'react';
import Albums from './Albums.js';
import Biography from './Biography.js';
import Panel from './Panel.js';

export default function ArtistPage({ artist }) {
  return (
    <>
      <h1>{artist.name}</h1>
      <Suspense fallback={<BigSpinner />}>
        <Biography artistId={artist.id} />
        <Suspense fallback={<AlbumsGlimmer />}>
          <Panel>
            <Albums artistId={artist.id} />
          </Panel>
        </Suspense>
      </Suspense>
    </>
  );
}

function BigSpinner() {
  return <h2>🌀 Loading...</h2>;
}

function AlbumsGlimmer() {
  return (
    <div className="glimmer-panel">
      <div className="glimmer-line" />
      <div className="glimmer-line" />
      <div className="glimmer-line" />
    </div>
  );
}

Suspense boundaries let you coordinate which parts of your UI should always “pop in” together at the same time, and which parts should progressively reveal more content in a sequence of loading states. You can add, move, or delete Suspense boundaries in any place in the tree without affecting the rest of your app’s behavior. Suspense 경계를 사용하면 UI의 어떤 부분이 항상 동시에 “등장”해야 하는지, 어떤 부분이 로딩 상태의 시퀀스에서 점진적으로 더 많은 콘텐츠를 표시해야 하는지 조정할 수 있습니다. 앱의 나머지 동작에 영향을 주지 않고 트리의 어느 위치에서나 Suspense 경계를 추가, 이동, 삭제할 수 있습니다.

Don’t put a Suspense boundary around every component. Suspense boundaries should not be more granular than the loading sequence that you want the user to experience. If you work with a designer, ask them where the loading states should be placed—it’s likely that they’ve already included them in their design wireframes. 모든 컴포넌트에 Suspense 경계를 설정하지 마세요. Suspense 경계는 사용자가 경험하게 될 로딩 시퀀스보다 더 세분화되어서는 안 됩니다. 디자이너와 함께 작업하는 경우 로딩 상태를 어디에 배치해야 하는지 디자이너에게 물어보세요. 디자이너가 이미 디자인 와이어프레임에 포함시켰을 가능성이 높습니다.


Showing stale content while fresh content is loading새 콘텐츠가 로드되는 동안 오래된 콘텐츠 표시하기

In this example, the SearchResults component suspends while fetching the search results. Type "a", wait for the results, and then edit it to "ab". The results for "a" will get replaced by the loading fallback. 아래 예제에서는 검색 결과를 가져오는 동안 SearchResults 컴포넌트가 일시 중단됩니다. "a"를 입력하고 결과를 기다린 다음 "ab"를 다시 입력하면 "a"에 대한 결과는 로딩 폴백으로 대체됩니다.

import { Suspense, useState } from 'react';
import SearchResults from './SearchResults.js';

export default function App() {
  const [query, setQuery] = useState('');
  return (
    <>
      <label>
        Search albums:
        <input value={query} onChange={e => setQuery(e.target.value)} />
      </label>
      <Suspense fallback={<h2>Loading...</h2>}>
        <SearchResults query={query} />
      </Suspense>
    </>
  );
}

A common alternative UI pattern is to defer updating the list and to keep showing the previous results until the new results are ready. The useDeferredValue Hook lets you pass a deferred version of the query down: 일반적인 대체 UI 패턴은 목록 업데이트를 연기하고 새 결과가 준비될 때까지 이전 결과를 계속 표시하는 것입니다. useDeferredValue 훅을 사용하면 쿼리의 지연된 버전을 전달할 수 있습니다:

export default function App() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
return (
<>
<label>
Search albums:
<input value={query} onChange={e => setQuery(e.target.value)} />
</label>
<Suspense fallback={<h2>Loading...</h2>}>
<SearchResults query={deferredQuery} />
</Suspense>
</>
);
}

The query will update immediately, so the input will display the new value. However, the deferredQuery will keep its previous value until the data has loaded, so SearchResults will show the stale results for a bit. query가 즉시 업데이트되므로 input에 새 값이 표시됩니다. 그러나 deferredQuery는 데이터가 로드될 때까지 이전 값을 유지하므로 SearchResults는 잠시 동안 이전 결과를 표시합니다.

To make it more obvious to the user, you can add a visual indication when the stale result list is displayed: 사용자에게 더 명확하게 알리기 위해 이전 결과 목록이 표시될 때 시각적 표시를 추가할 수 있습니다:

<div style={{
opacity: query !== deferredQuery ? 0.5 : 1
}}>
<SearchResults query={deferredQuery} />
</div>

Enter "a" in the example below, wait for the results to load, and then edit the input to "ab". Notice how instead of the Suspense fallback, you now see the dimmed stale result list until the new results have loaded: 아래 예제에서 "a"를 입력하고 결과가 로드될 때까지 기다린 다음 입력을 "ab"로 변경해 보세요. 이제 새 결과가 로드될 때까지 일시 중단 폴백 대신 이전 결과 목록이 흐리게 표시되는 것을 확인할 수 있습니다:

import { Suspense, useState, useDeferredValue } from 'react';
import SearchResults from './SearchResults.js';

export default function App() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  const isStale = query !== deferredQuery;
  return (
    <>
      <label>
        Search albums:
        <input value={query} onChange={e => setQuery(e.target.value)} />
      </label>
      <Suspense fallback={<h2>Loading...</h2>}>
        <div style={{ opacity: isStale ? 0.5 : 1 }}>
          <SearchResults query={deferredQuery} />
        </div>
      </Suspense>
    </>
  );
}

Note

Both deferred values and Transitions let you avoid showing Suspense fallback in favor of inline indicators. Transitions mark the whole update as non-urgent so they are typically used by frameworks and router libraries for navigation. Deferred values, on the other hand, are mostly useful in application code where you want to mark a part of UI as non-urgent and let it “lag behind” the rest of the UI. 지연값과 트랜지션을 모두 사용하면 인라인 표시기를 위해 Suspense 폴백을 표시하지 않을 수 있습니다. 트랜지션은 전체 업데이트를 긴급하지 않은 것으로 표시하므로, 일반적으로 프레임워크 및 라우터 라이브러리에서 탐색을 위해 사용됩니다. 반면 지연값은 주로 UI의 일부를 긴급하지 않은 것으로 표시함으로써, 다른 UI들보다 “지연”시키고자 할 때 유용합니다.


Preventing already revealed content from hiding이미 표시된 콘텐츠가 숨겨지지 않도록 방지하기

When a component suspends, the closest parent Suspense boundary switches to showing the fallback. This can lead to a jarring user experience if it was already displaying some content. Try pressing this button: 컴포넌트가 일시 중단되면 가장 가까운 상위 Suspense 경계가 폴백으로 전환됩니다. 이미 일부 콘텐츠가 표시되고 있는 경우 사용자 경험이 끊길 수 있습니다. 아래 예제의 버튼을 눌러 보세요:

import { Suspense, useState } from 'react';
import IndexPage from './IndexPage.js';
import ArtistPage from './ArtistPage.js';
import Layout from './Layout.js';

export default function App() {
  return (
    <Suspense fallback={<BigSpinner />}>
      <Router />
    </Suspense>
  );
}

function Router() {
  const [page, setPage] = useState('/');

  function navigate(url) {
    setPage(url);
  }

  let content;
  if (page === '/') {
    content = (
      <IndexPage navigate={navigate} />
    );
  } else if (page === '/the-beatles') {
    content = (
      <ArtistPage
        artist={{
          id: 'the-beatles',
          name: 'The Beatles',
        }}
      />
    );
  }
  return (
    <Layout>
      {content}
    </Layout>
  );
}

function BigSpinner() {
  return <h2>🌀 Loading...</h2>;
}

When you pressed the button, the Router component rendered ArtistPage instead of IndexPage. A component inside ArtistPage suspended, so the closest Suspense boundary started showing the fallback. The closest Suspense boundary was near the root, so the whole site layout got replaced by BigSpinner. 버튼을 누르자 Router 컴포넌트가 IndexPage 대신 ArtistPage를 렌더링했습니다. ArtistPage 내부의 컴포넌트가 일시 중단되었기 때문에 가장 가까운 Suspense 경계가 폴백을 표시하기 시작했습니다. 가장 가까운 Suspense 경계는 루트 근처에 있었기 때문에 전체 사이트 레이아웃이 BigSpinner로 대체되었습니다.

To prevent this, you can mark the navigation state update as a Transition with startTransition: 이를 방지하려면 startTransition을 사용하여 탐색 state 업데이트를 트랜지션으로 표시할 수 있습니다:

function Router() {
const [page, setPage] = useState('/');

function navigate(url) {
startTransition(() => {
setPage(url);
});
}
// ...

This tells React that the state transition is not urgent, and it’s better to keep showing the previous page instead of hiding any already revealed content. Now clicking the button “waits” for the Biography to load: 이 방법은 React에게 state 전환이 긴급하지 않으며 이미 표시된 콘텐츠를 숨기는 대신 이전 페이지를 계속 표시하는 것이 낫다고 알려줍니다. 이제 버튼을 클릭하면 Biography가 로드될 때까지 “대기” 상태가 됩니다:

import { Suspense, startTransition, useState } from 'react';
import IndexPage from './IndexPage.js';
import ArtistPage from './ArtistPage.js';
import Layout from './Layout.js';

export default function App() {
  return (
    <Suspense fallback={<BigSpinner />}>
      <Router />
    </Suspense>
  );
}

function Router() {
  const [page, setPage] = useState('/');

  function navigate(url) {
    startTransition(() => {
      setPage(url);
    });
  }

  let content;
  if (page === '/') {
    content = (
      <IndexPage navigate={navigate} />
    );
  } else if (page === '/the-beatles') {
    content = (
      <ArtistPage
        artist={{
          id: 'the-beatles',
          name: 'The Beatles',
        }}
      />
    );
  }
  return (
    <Layout>
      {content}
    </Layout>
  );
}

function BigSpinner() {
  return <h2>🌀 Loading...</h2>;
}

A Transition doesn’t wait for all content to load. It only waits long enough to avoid hiding already revealed content. For example, the website Layout was already revealed, so it would be bad to hide it behind a loading spinner. However, the nested Suspense boundary around Albums is new, so the Transition doesn’t wait for it. 트랜지션은 모든 콘텐츠가 로드될 때까지 기다리지 않습니다. 오직 이미 표시된 콘텐츠가 숨겨지지 않을 만큼만 기다립니다. 예를 들어, 웹사이트 Layout이 이미 표시된 경우 이를 다시 로딩 스피너 뒤로 숨기는 것은 좋지 않을 것입니다. 그러나 Albums 주위의 중첩된 Suspense 경계는 새로운 것이므로, 트랜지션은 이를 기다리지 않습니다.

Note

Suspense-enabled routers are expected to wrap the navigation updates into Transitions by default. Suspense가 도입된 라우터는 기본적으로 탐색 업데이트를 트랜지션으로 감싸고 있을 것입니다.


Indicating that a Transition is happening트랜지션이 발생하고 있음을 나타내기

In the above example, once you click the button, there is no visual indication that a navigation is in progress. To add an indicator, you can replace startTransition with useTransition which gives you a boolean isPending value. In the example below, it’s used to change the website header styling while a Transition is happening: 위의 예제에서는 버튼을 클릭해도 탐색이 진행 중이라는 시각적 표시가 없습니다. 표시기를 추가하려면 startTransitionisPending 이라는 불리언 값을 제공하는 useTransition으로 대체하면 됩니다. 아래 예제에서는 트랜지션이 진행되는 동안 웹사이트 헤더 스타일을 변경하는 데 사용됩니다:

import { Suspense, useState, useTransition } from 'react';
import IndexPage from './IndexPage.js';
import ArtistPage from './ArtistPage.js';
import Layout from './Layout.js';

export default function App() {
  return (
    <Suspense fallback={<BigSpinner />}>
      <Router />
    </Suspense>
  );
}

function Router() {
  const [page, setPage] = useState('/');
  const [isPending, startTransition] = useTransition();

  function navigate(url) {
    startTransition(() => {
      setPage(url);
    });
  }

  let content;
  if (page === '/') {
    content = (
      <IndexPage navigate={navigate} />
    );
  } else if (page === '/the-beatles') {
    content = (
      <ArtistPage
        artist={{
          id: 'the-beatles',
          name: 'The Beatles',
        }}
      />
    );
  }
  return (
    <Layout isPending={isPending}>
      {content}
    </Layout>
  );
}

function BigSpinner() {
  return <h2>🌀 Loading...</h2>;
}


Resetting Suspense boundaries on navigation탐색시 Suspense 경계 재설정하기

During a Transition, React will avoid hiding already revealed content. However, if you navigate to a route with different parameters, you might want to tell React it is different content. You can express this with a key: 트랜지션하는 동안 React는 이미 표시된 콘텐츠를 숨기지 않습니다. 하지만 다른 매개변수가 있는 경로로 이동하는 경우 React에게 다른 콘텐츠라고 알려줄 필요가 있습니다. Key를 사용하여 표현할 수 있습니다:

<ProfilePage key={queryParams.id} />

Imagine you’re navigating within a user’s profile page, and something suspends. If that update is wrapped in a Transition, it will not trigger the fallback for already visible content. That’s the expected behavior. 어떤 사용자의 프로필 페이지를 둘러보던 중에 무언가가 일시 중단되었다고 가정해 봅시다. 해당 업데이트가 트랜지션으로 감싸져 있으면 이미 표시된 콘텐츠에 대한 폴백이 촉발되지 않을 것입니다. 이는 예상되는 동작입니다.

However, now imagine you’re navigating between two different user profiles. In that case, it makes sense to show the fallback. For example, one user’s timeline is different content from another user’s timeline. By specifying a key, you ensure that React treats different users’ profiles as different components, and resets the Suspense boundaries during navigation. Suspense-integrated routers should do this automatically. 이번에는 두 개의 서로 다른 사용자 프로필 사이를 탐색하고 있다고 가정해 봅시다. 이 경우에는 폴백을 표시하는 것이 좋을 것입니다. 예를 들어, 한 사용자의 타임라인은 다른 사용자의 타임라인과 다른 콘텐츠입니다. Key를 지정하면 React가 서로 다른 사용자의 프로필을 서로 다른 컴포넌트로 취급하고 탐색 중에 Suspense 경계를 재설정하도록 할 수 있습니다. Suspense 통합 라우터는 이 작업을 자동으로 수행해야 합니다.


Providing a fallback for server errors and client-only content서버 오류 및 클라이언트 전용 콘텐츠에 대한 폴백 제공하기

If you use one of the streaming server rendering APIs (or a framework that relies on them), React will also use your <Suspense> boundaries to handle errors on the server. If a component throws an error on the server, React will not abort the server render. Instead, it will find the closest <Suspense> component above it and include its fallback (such as a spinner) into the generated server HTML. The user will see a spinner at first. 스트리밍 서버 렌더링 API 중 하나 (또는 이에 의존하는 프레임워크)를 사용하는 경우, React는 서버에서 발생하는 오류를 처리하기 위해 <Suspense> 바운더리도 사용합니다. 컴포넌트가 서버에서 에러를 발생시키더라도 React는 서버 렌더링을 중단하지 않습니다. 대신, 그 위에 있는 가장 가까운 <Suspense> 컴포넌트를 찾아서 생성된 서버 HTML에 그 폴백(예: 스피너)을 포함시킵니다. 사용자는 처음에 스피너를 보게 됩니다.

On the client, React will attempt to render the same component again. If it errors on the client too, React will throw the error and display the closest error boundary. However, if it does not error on the client, React will not display the error to the user since the content was eventually displayed successfully. 클라이언트에서 React는 동일한 컴포넌트를 다시 렌더링하려고 시도합니다. 클라이언트에서도 에러가 발생하면 React는 에러를 던지고 가장 가까운 에러 경계를 표시합니다. 그러나 클라이언트에서 에러가 발생하지 않는다면, 결국 콘텐츠가 성공적으로 표시되었기 때문에 React는 사용자에게 에러를 표시하지 않습니다.

You can use this to opt out some components from rendering on the server. To do this, throw an error in the server environment and then wrap them in a <Suspense> boundary to replace their HTML with fallbacks: 이를 이용하여 일부 컴포넌트를 서버에서 렌더링하지 않도록 선택할 수 있습니다. 이렇게 하려면 서버 환경에서 오류를 발생시킨 다음 해당 컴포넌트를 <Suspense> 경계로 감싸서 해당 HTML을 폴백으로 대체하면 됩니다:

<Suspense fallback={<Loading />}>
<Chat />
</Suspense>

function Chat() {
if (typeof window === 'undefined') {
throw Error('Chat should only render on the client.');
}
// ...
}

The server HTML will include the loading indicator. It will be replaced by the Chat component on the client. 서버 HTML에는 로딩 표시기가 포함됩니다. 이 표시기는 클라이언트에서는 Chat 컴포넌트로 대체됩니다.


Troubleshooting문제 해결

How do I prevent the UI from being replaced by a fallback during an update?업데이트 중에 UI가 폴백으로 대체되는 것을 방지하려면 어떻게 해야 할까요?

Replacing visible UI with a fallback creates a jarring user experience. This can happen when an update causes a component to suspend, and the nearest Suspense boundary is already showing content to the user. 표시되는 UI를 폴백으로 대체하면 사용자 환경이 불안정해집니다. 이는 업데이트로 인해 컴포넌트가 일시 중단되었는데 가장 가까운 Suspense 경계에는 이미 콘텐츠가 표시되고 있을 때 발생할 수 있습니다.

To prevent this from happening, mark the update as non-urgent using startTransition. During a Transition, React will wait until enough data has loaded to prevent an unwanted fallback from appearing: 이런 일이 발생하지 않도록 하려면 startTransition을 사용하여 업데이트를 긴급하지 않은 것으로 표시하세요. 트랜지션이 진행되는 동안 React는 원치 않는 폴백이 나타나지 않기에 충분한 데이터가 로드될 때까지 기다립니다:

function handleNextPageClick() {
// If this update suspends, don't hide the already displayed content
// 이 업데이트가 일시 중단되어도 이미 표시된 콘텐츠를 숨기지 않습니다
startTransition(() => {
setCurrentPage(currentPage + 1);
});
}

This will avoid hiding existing content. However, any newly rendered Suspense boundaries will still immediately display fallbacks to avoid blocking the UI and let the user see the content as it becomes available. 이렇게 하면 기존 콘텐츠가 숨겨지지 않습니다. 반면 새로 렌더링된 Suspense 경계는 여전히 UI를 가리지 않기 위해 즉시 폴백을 표시하며, 콘텐츠는 사용할 수 있게 될 때 노출합니다.

React will only prevent unwanted fallbacks during non-urgent updates. It will not delay a render if it’s the result of an urgent update. You must opt in with an API like startTransition or useDeferredValue. React는 긴급하지 않은 업데이트 중에만 원치 않는 폴백을 방지합니다. 긴급한 업데이트의 결과인 경우 렌더링을 지연시키지 않습니다. startTransition 또는 useDeferredValue와 같은 API를 사용해야 합니다.

If your router is integrated with Suspense, it should wrap its updates into startTransition automatically. 라우터가 Suspense와 통합되어 있는 경우, 라우터는 업데이트를 startTransition으로 자동으로 감싸야 합니다.