본문으로 건너뛰기
개발 머꼬
개발 노트Next.js
hohyeon.dev22

loading.tsx를 만들었는데 로딩 화면이 안 보인 이유

  • #Engineering Note
  • #Next.js
  • #Performance

문제 발생

목록 페이지에 스켈레톤을 붙였는데 화면이 그냥 멈춰 있다가 완성된 페이지로 바뀌었습니다.

app/dashboard/
  layout.tsx    # 세션을 읽어 사이드바에 사용자 이름 표시
  loading.tsx   # 스켈레톤
  page.tsx      # 목록 조회

page.tsx의 쿼리를 일부러 느리게 만들어도 스켈레톤은 보이지 않았습니다.

원인 분석

loading.js가 감싸는 범위는 정해져 있습니다. Next.js 문서가 명시합니다 — 컴포넌트 계층에서 loading.jsnot-found.js, page.js, 그리고 하위의 layout.js 파일들을 <Suspense> 경계로 감싸며, 같은 세그먼트의 layout.js, template.js, error.js는 감싸지 않습니다.

그리고 바로 이어지는 주의가 이번 원인입니다 — 레이아웃이 캐시되지 않은 데이터나 런타임 데이터(cookies(), headers(), 캐시되지 않은 fetch 등)에 접근하면 loading.js는 그에 대한 폴백을 보여주지 않습니다. Cache Components를 쓰지 않는 경우 레이아웃 렌더링이 끝날 때까지 내비게이션이 막힙니다.

layout.tsx가 세션을 읽고 있었으니, 스켈레톤이 나올 자리 자체가 없었습니다.

해결 방안

  1. 문서가 안내하는 두 가지 중 하나를 고릅니다캐시되지 않은 데이터 페칭을 layout.js에서 page.js로 옮기거나, 레이아웃의 런타임 데이터 접근을 자체 <Suspense> 경계로 감쌉니다.
// app/dashboard/layout.tsx
<Suspense fallback={<UserChipSkeleton />}>
  <UserChip />   {/* 여기서만 세션을 읽는다 */}
</Suspense>
  1. 세그먼트 하나에 전부 맡기지 않습니다. 문서 설명대로 loading.js 외에 직접 <Suspense>를 두면 페이지 안에서 느린 조각만 따로 스트리밍됩니다. 목록과 사이드 위젯이 서로를 기다릴 이유가 없습니다.

  2. 스켈레톤을 실제 레이아웃에 맞춥니다. 문서가 권하는 것도 스피너가 아니라 커버 이미지, 제목처럼 앞으로 보일 화면의 작지만 의미 있는 일부입니다. 모양이 다르면 스켈레톤이 사라질 때 레이아웃이 튑니다.

  3. 스트리밍의 부작용을 알아둡니다. 문서가 명시합니다 — 스트리밍이 시작되면 응답은 성공을 뜻하는 200으로 나가고, 응답 헤더가 이미 전송되었기 때문에 상태 코드를 나중에 바꿀 수 없습니다. 그래서 스트리밍된 404 화면에는 Next.js가 <meta name="robots" content="noindex">를 넣어 색인을 막습니다. 진짜 404 상태 코드가 필요하면 본문 스트리밍 전에 존재 여부를 확인해야 합니다.

  4. 개발에서 "잘 되는 것처럼" 보이는 것에 속지 않습니다. 로컬 DB는 빠르고 세션 조회도 즉시 끝나 폴백이 스쳐 지나갑니다. 지연을 인위적으로 넣어 실제로 무엇이 먼저 보이는지 확인합니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.