Skip to content

[FEAT] 에러/로딩/온보딩 상태 페이지 Lottie 애니메이션 적용#131

Open
kimminna wants to merge 3 commits into
developfrom
feat/web/130-status-screen-lottie
Open

[FEAT] 에러/로딩/온보딩 상태 페이지 Lottie 애니메이션 적용#131
kimminna wants to merge 3 commits into
developfrom
feat/web/130-status-screen-lottie

Conversation

@kimminna

@kimminna kimminna commented Jul 9, 2026

Copy link
Copy Markdown
Member

ISSUE 🔗

close #130



What is this PR? 🔍

전역 에러(500), 404, 로딩, 온보딩 화면에 공통 StatusScreen/LottiePlayer 컴포넌트를 도입해 Lottie 애니메이션 기반 상태 화면으로 정비했습니다.

배경

  • 기존 구조: global-error.tsx만 최소한으로 존재했고, 404·500 전용 페이지와 로딩 화면은 없었으며 상태별 안내 문구도 별도로 관리되지 않았습니다.
  • 발생 문제: 에러 발생 시 사용자에게 상황(일시 장애/존재하지 않는 페이지/전역 크래시)에 맞는 안내를 주지 못했고, 페이지 전환 로딩 중에도 빈 화면이 노출됐습니다.
  • 해결 방향: 상태 화면 전용 공통 컴포넌트(StatusScreen)와 Lottie 렌더러(LottiePlayer)를 만들고, 각 상태(500/404/전역 에러/로딩/온보딩)마다 해당 Lottie 애니메이션과 i18n 문구를 매핑했습니다.

LottiePlayer / StatusScreen 공통 컴포넌트

  • 변경 요약: lottie-react 기반 LottiePlayer와, 타이틀·설명·액션 버튼을 배치하는 StatusScreen을 새로 추가했습니다.
  • 이유: 404/500/전역 에러 화면이 레이아웃(Lottie + 타이틀 + 설명 + 홈 버튼)을 동일하게 공유해 매번 마크업을 반복할 필요가 없었습니다.
  • 구현 방식: LottiePlayersrc로 받은 경로를 fetch해 JSON 애니메이션 데이터를 상태로 들고 있다가 로드되면 Lottie 컴포넌트를 렌더링합니다. 로드 실패 시에는 컨테이너만 비워 두어 레이아웃 시프트를 방지합니다. StatusScreenlottieSrc/title/description/action을 props로 받아 중앙 정렬 레이아웃을 구성합니다.

상태 페이지 (500 / 404 / 전역 에러 / 로딩)

  • 변경 요약: [locale]/error.tsx(500), [locale]/not-found.tsx(404), global-error.tsx(전역 크래시), [locale]/loading.tsx를 새로 추가/정비하고, 404 유도를 위한 [locale]/[...rest] catch-all 라우트를 추가했습니다.
  • 이유: Next.js App Router 컨벤션상 각 상태를 별도 파일로 분리해야 해당 상태에서만 렌더링되며, catch-all 라우트가 없으면 정의되지 않은 경로가 404로 떨어지지 않습니다.
  • 구현 방식: 각 페이지는 StatusScreen에 상태별 lottieSrc(/lottie/500.json, 404.json, error.json)와 홈으로 이동하는 action을 전달합니다. loading.tsx는 텍스트 없이 LottiePlayer만 화면 중앙에 배치했습니다. global-error.tsx<html>/<body>를 직접 렌더링해야 하는 Next.js 제약 때문에 NextIntlClientProvider로 메시지를 직접 주입하고, 최초 렌더 시 URL 세그먼트에서 locale을 추출해 폰트·언어를 맞췄습니다. 또한 500/404/전역 에러 화면이 각각 별도로 정의된 Error/NotFound/GlobalError 번역 네임스페이스 대신 전부 Error를 참조하고 있던 것을 발견해, 상태별로 올바른 네임스페이스를 참조하도록 수정했습니다.
  • 경계 · 제약: global-error.tsx는 클라이언트 컴포넌트로 유지되며 Sentry.captureException으로 에러를 보고합니다.

온보딩 페이지

  • 변경 요약: 온보딩 페이지에 LottiePlayer로 온보딩 애니메이션(/lottie/onboarding.json)을 노출했습니다.
  • 이유: 기존에는 빈 페이지였고, 같은 Lottie 도입 작업에서 함께 준비된 에셋을 반영했습니다.
  • 구현 방식: 화면 좌측에 고정 위치(top-1/2 left-[197px])로 애니메이션을 배치했습니다.



To Reviewers

  • global-error.tsx는 Next.js 특성상 <html>/<body>를 직접 감싸고 locale을 클라이언트에서 뒤늦게 결정하는 구조라 최초 렌더에는 기본 locale(routing.defaultLocale)로 잠깐 보였다가 바뀝니다. 이 전제가 맞는지 확인 부탁드립니다.
  • 온보딩 페이지 변경은 이슈 [FEAT] 에러/로딩 상태 페이지 Lottie 애니메이션 적용 #130(에러/로딩 상태 페이지) 범위와는 결이 다르지만, 같은 Lottie 에셋 작업 세션에서 나온 변경이라 같은 브랜치에 포함했습니다. 별도 이슈로 분리가 필요하면 말씀해 주세요.
  • 이번 세션에서는 브라우저로 직접 화면을 띄워보지 못해 실제 렌더링 스크린샷은 없습니다. 병합 전에 404/500/로딩/온보딩 화면을 한 번씩 확인해 주시면 좋겠습니다.



Screenshot 📷



Test Checklist ✔

  • pnpm check-types 통과
  • pnpm lint 통과
  • 404/500/전역 에러/로딩/온보딩 화면 브라우저 확인 — 미실행: 리뷰 시 확인 필요

kimminna added 3 commits July 10, 2026 00:17
- 상태 페이지 애니메이션 렌더링을 위해 lottie-react 패키지를 추가했습니다
- 공통 LottiePlayer, StatusScreen 컴포넌트를 추가했습니다
- global-error, 500, 404 페이지에 상태별 Lottie 애니메이션과 안내 문구를 적용했습니다
- loading 페이지를 로띠 애니메이션만 중앙에 노출하도록 구성했습니다
- 404 응답을 유도하기 위한 [...rest] catch-all 라우트를 추가했습니다
- 500/404/전역 에러 문구가 각각 올바른 번역 네임스페이스를 참조하도록 수정했습니다
- 온보딩 페이지에 LottiePlayer로 온보딩 애니메이션을 노출했습니다
@vercel

vercel Bot commented Jul 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
timo Ready Ready Preview, Comment Jul 9, 2026 3:22pm

@github-actions github-actions Bot requested review from ehye1 and jjangminii July 9, 2026 15:22
@github-actions github-actions Bot added ⏰ Timo-web Timo 웹 서비스 ✨ Feature 새로운 기능(기능성) 구현 ♦️ 민아 민아상 labels Jul 9, 2026
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

전역 에러, 500 에러, 404, 로딩, 온보딩, 캐치올 라우트에 공통 LottiePlayer/StatusScreen 컴포넌트 기반 UI를 도입했습니다. lottie-react 의존성, en.json/ko.json 번역 키, error.json Lottie 애셋이 추가되었고, global-error.tsx는 next-intl 로케일/메시지 동적 처리로 개편되었습니다.

Changes

상태 페이지 Lottie 적용

Layer / File(s) Summary
LottiePlayer 공통 컴포넌트
apps/timo-web/components/lottie/LottiePlayer.tsx, apps/timo-web/package.json
src를 fetch해 Lottie JSON을 로드하고 AbortController로 취소를 지원하며, 로드 성공 시에만 Lottie를 렌더링하는 클라이언트 컴포넌트와 lottie-react 의존성을 추가했습니다.
StatusScreen 공통 컴포넌트
apps/timo-web/components/status-screen/StatusScreen.tsx
lottieSrc, title, description, action을 받아 중앙 정렬 레이아웃으로 렌더링하는 컴포넌트를 추가했습니다.
전역 에러 페이지 리뉴얼
apps/timo-web/app/global-error.tsx
로컬 폰트와 경로 기반 로케일 추출/메시지 매핑을 추가하고, 고정 문구 대신 GlobalErrorContent/StatusScreen 렌더링으로 교체하며 reset 사용을 제거했습니다(Sentry 캡처는 유지).
500 에러 페이지
apps/timo-web/app/[locale]/error.tsx
useTranslations("Error")로 텍스트를 준비하고 useEffect에서 Sentry.captureException을 호출하며 StatusScreen에 500 Lottie와 홈 버튼을 전달합니다.
404 페이지 및 캐치올 라우트
apps/timo-web/app/[locale]/not-found.tsx, apps/timo-web/app/[locale]/[...rest]/page.tsx
getTranslations("NotFound") 기반 StatusScreen을 렌더링하는 NotFound와, 항상 notFound()를 호출하는 CatchAll 라우트를 추가했습니다.
로딩/온보딩 Lottie 적용
apps/timo-web/app/[locale]/loading.tsx, apps/timo-web/app/[locale]/onboarding/page.tsx
로딩 Lottie를 중앙 배치하는 Loading 컴포넌트를 추가하고, 온보딩 페이지가 빈 프래그먼트 대신 온보딩 Lottie를 렌더링하도록 수정했습니다.
번역 리소스 및 Lottie 애셋
apps/timo-web/messages/en.json, apps/timo-web/messages/ko.json, apps/timo-web/public/lottie/error.json
Error, NotFound, GlobalError 번역 키를 추가하고 500 상태용 Lottie 애니메이션 JSON을 추가했습니다.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant ErrorPage as "ErrorPage / GlobalErrorContent"
  participant Sentry
  participant StatusScreen
  participant LottiePlayer

  User->>ErrorPage: 라우트에서 에러 발생
  ErrorPage->>Sentry: captureException(error)
  ErrorPage->>StatusScreen: title/description/action 전달
  StatusScreen->>LottiePlayer: lottieSrc, ariaLabel 전달
  LottiePlayer->>LottiePlayer: fetch(src)로 애니메이션 로드
  LottiePlayer-->>StatusScreen: animationData 렌더링
  StatusScreen-->>User: 상태 화면 표시 (홈 버튼 포함)
Loading

Possibly related PRs

  • Team-Timo/Timo-client#27: 동일한 global-error.tsx에서 Sentry captureException 처리 및 렌더링 구조를 다루는 초기 구현.
  • Team-Timo/Timo-client#38: 동일 파일 onboarding/page.tsx의 빈 placeholder 구현을 후속으로 대체하는 변경.
  • Team-Timo/Timo-client#105: next-intl 로케일/메시지 인프라를 그대로 활용해 번역 기반 상태 화면을 구성.

Suggested labels: ♥️ 혜원

Suggested reviewers: ehye1, yumin-kim2, jjangminii

리뷰 한마디: Lottie 애니메이션까지 챙기신 센스, 에러 화면도 이제 시각적으로 우아하게 실패하네요 ✨ 다만 ko.jsonError.description에 오타(“주새요”)가 보이니 한 번만 다듬어 주시면 완벽할 것 같습니다. i18n 메시지 관리 관련해서는 next-intl 공식 문서(https://next-intl.dev/docs/usage/messages)를 참고하시면 도움이 될 거예요.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning 온보딩 페이지의 Lottie 적용은 이슈 #130의 상태 페이지 범위를 벗어난 별도 변경입니다. 온보딩 변경은 별도 PR로 분리하고, 본 PR에는 에러/로딩/404/전역 에러 관련 변경만 남기세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed 공통 컴포넌트, global-error, 404/500/loading, catch-all, 번역, lottie-react 추가가 이슈 요구사항과 맞습니다.
Title check ✅ Passed 주요 변경점인 상태 화면의 Lottie 적용을 잘 요약했고, 에러·로딩·온보딩 범위도 대체로 맞습니다.
Description check ✅ Passed 설명이 실제 변경사항(StatusScreen/LottiePlayer, 404/500/로딩/전역 에러, 번역, 의존성)과 잘 맞습니다.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/web/130-status-screen-lottie

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

Storybook Preview

항목 링크
Storybook 열기
Chromatic 빌드 확인

마지막 업데이트: 2026-07-09 15:23 UTC

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

Timo Performance Report

Bundle Size — timo-web
라우트 크기 First Load JS
/[locale] 0 B 🟡 205.81 kB
/[locale]/focus 0 B 🟡 205.81 kB
/[locale]/home 0 B 🟡 205.81 kB
/[locale]/onboarding 0 B 🟡 205.81 kB
/[locale]/settings 0 B 🟡 205.81 kB
/[locale]/settings/account 0 B 🟡 205.81 kB
/[locale]/settings/policy 0 B 🟡 205.81 kB
/[locale]/statistics 0 B 🟡 205.81 kB
/[locale]/today 0 B 🟡 205.81 kB
/[locale]/[...rest] 0 B 🟡 205.81 kB

공유 번들: 205.81 kB
🟢 < 200kB  |  🟡 < 350kB  |  🔴 ≥ 350kB (First Load JS · gzip)

Lighthouse — timo-web

⚠️ Lighthouse 결과를 가져오지 못했습니다.

Image Optimization — timo-web

public/ 디렉토리에 이미지가 없습니다.

측정 커밋: 5abc383

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/timo-web/app/`[locale]/loading.tsx:
- Around line 5-7: `LottiePlayer` is rendered without an accessible name, so
update the loading UI in the `loading.tsx` component to pass a meaningful
`ariaLabel` alongside the existing `src` and `className` props. Use the
`LottiePlayer` usage in this screen to ensure it still renders as `role="img"`
but now has a screen-reader-friendly label that describes the loading state.

In `@apps/timo-web/app/`[locale]/onboarding/page.tsx:
- Around line 5-9: The onboarding page animation in the LottiePlayer usage is
hard-coded for desktop and overflows on small screens, and it also lacks an
accessibility decision. Update the LottiePlayer configuration to use responsive
positioning/sizing in the onboarding page component (for example by adjusting
the current absolute placement across breakpoints or making the desktop-only
intent explicit), and add an appropriate ariaLabel if the animation conveys
meaning or hide it from assistive tech if it is decorative.

In `@apps/timo-web/app/global-error.tsx`:
- Around line 50-58: 중복된 상태 화면 액션 버튼(Link) 구현을 공통 컴포넌트로 추출하세요. global-error.tsx의
action, error.tsx의 action, not-found.tsx의 버튼이 동일한 스타일과 거의 같은 구조를 반복하므로,
StatusActionButton 같은 재사용 컴포넌트를 만들어 href와 label만 받도록 정리하고 세 파일에서 이를 사용하세요. 또한
global-error.tsx와 error.tsx의 wrapping div와 not-found.tsx의 단일 Link 구조 차이를 함께 정리해,
각 화면의 action 렌더링 형태가 일관되게 맞도록 수정하세요.

In `@apps/timo-web/components/lottie/LottiePlayer.tsx`:
- Around line 22-33: In LottiePlayer’s fetch effect, the current response
handling passes every resolved fetch directly to response.json(), which allows
404/500 responses to be treated as valid animation data. Update the useEffect
flow to check response.ok before parsing, and throw an error when it is false so
the failure is routed into the existing catch path. Keep the fix localized to
the fetch chain in LottiePlayer so only valid animation JSON reaches
setAnimationData.

In `@apps/timo-web/messages/ko.json`:
- Around line 44-57: Fix the Korean localization text in ko.json by correcting
the typos in Error.description (“잠시후” to “잠시 후” and “주새요” to “주세요”) and making
punctuation consistent by adding a period to NotFound.description; update the
affected entries in the Error and NotFound message objects so the wording and
sentence endings match the rest of the translations.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 802cb16c-4a85-4edd-b90e-0e30c119f8d0

📥 Commits

Reviewing files that changed from the base of the PR and between 6e082b2 and 5ee988e.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (16)
  • apps/timo-web/app/[locale]/[...rest]/page.tsx
  • apps/timo-web/app/[locale]/error.tsx
  • apps/timo-web/app/[locale]/loading.tsx
  • apps/timo-web/app/[locale]/not-found.tsx
  • apps/timo-web/app/[locale]/onboarding/page.tsx
  • apps/timo-web/app/global-error.tsx
  • apps/timo-web/components/lottie/LottiePlayer.tsx
  • apps/timo-web/components/status-screen/StatusScreen.tsx
  • apps/timo-web/messages/en.json
  • apps/timo-web/messages/ko.json
  • apps/timo-web/package.json
  • apps/timo-web/public/lottie/404.json
  • apps/timo-web/public/lottie/500.json
  • apps/timo-web/public/lottie/error.json
  • apps/timo-web/public/lottie/loading.json
  • apps/timo-web/public/lottie/onboarding.json

Comment thread apps/timo-web/app/[locale]/loading.tsx
Comment on lines +5 to +9
<div className="relative h-screen w-full">
<LottiePlayer
src="/lottie/onboarding.json"
className="absolute top-1/2 left-[197px] w-[500px] -translate-y-1/2"
/>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 온보딩 페이지가 데스크톱 전용인지, 반응형 대응이 필요한지 확인합니다.
# 1. 미디어 쿼리나 반응형 클래스 사용 여부 검색
rg -n 'md:|lg:|sm:|xl:' apps/timo-web/app/[locale]/onboarding/

# 2. 다른 페이지의 반응형 패턴 확인
rg -n 'left-\[|w-\[500' apps/timo-web/app/ --type=ts

# 3. Viewport meta 태그 또는 반응형 설정 확인
rg -n 'viewport|max-width|min-width' apps/timo-web/app/[locale]/layout.tsx

Repository: Team-Timo/Timo-client

Length of output: 285


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- onboarding page ---'
sed -n '1,200p' apps/timo-web/app/[locale]/onboarding/page.tsx | cat -n

echo
echo '--- find LottiePlayer definition/usages ---'
fd -a 'LottiePlayer' apps/timo-web components | sed -n '1,120p' || true
rg -n "export .*LottiePlayer|function LottiePlayer|const LottiePlayer|ariaLabel|aria-label" apps/timo-web components -g '!**/node_modules/**' | sed -n '1,200p' || true

echo
echo '--- list lottie component files ---'
fd -a '' components/lottie apps/timo-web/components/lottie 2>/dev/null | sed -n '1,200p' || true

Repository: Team-Timo/Timo-client

Length of output: 1763


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- onboarding-related files ---'
fd -a 'onboarding' apps/timo-web | sed -n '1,200p'

echo
echo '--- onboarding page references ---'
rg -n "onboarding" apps/timo-web -g '!**/node_modules/**' | sed -n '1,200p'

echo
echo '--- LottiePlayer definition ---'
sed -n '1,220p' apps/timo-web/components/lottie/LottiePlayer.tsx | cat -n

echo
echo '--- relevant layout/page sizing hints ---'
sed -n '1,220p' apps/timo-web/app/[locale]/layout.tsx | cat -n

Repository: Team-Timo/Timo-client

Length of output: 3587


온보딩 애니메이션에 반응형/접근성 레일을 깔아주세요
레이아웃 뼈대는 깔끔합니다. 다만 left-[197px] + w-[500px] 조합은 375px 폭에서 오른쪽으로 크게 넘쳐 보입니다. sm/md에서 위치·크기를 조정하거나, 정말 데스크톱 전용이면 의도를 코드에 드러내 주세요.
또한 LottiePlayerariaLabel을 지원하므로, 의미 있는 안내라면 라벨을 넣고 장식용이라면 숨김 처리하는 쪽이 맞습니다.
참고: MDN ARIA, Next.js Accessibility

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/timo-web/app/`[locale]/onboarding/page.tsx around lines 5 - 9, The
onboarding page animation in the LottiePlayer usage is hard-coded for desktop
and overflows on small screens, and it also lacks an accessibility decision.
Update the LottiePlayer configuration to use responsive positioning/sizing in
the onboarding page component (for example by adjusting the current absolute
placement across breakpoints or making the desktop-only intent explicit), and
add an appropriate ariaLabel if the animation conveys meaning or hide it from
assistive tech if it is decorative.

Comment on lines +50 to +58
action={
<div className="flex items-center gap-3">
<Link
href="/"
className="typo-headline-m-16 rounded-[8px] border border-gray-500 px-4 py-2.5 text-gray-900"
>
{t("homeButton")}
</Link>
</div>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

액션 버튼 코드 중복 — 공통 컴포넌트 추출을 제안합니다.

global-error.tsx, error.tsx(26-34행), not-found.tsx(14-21행) 세 파일에서 동일한 Link 버튼(typo-headline-m-16 rounded-[8px] border border-gray-500 px-4 py-2.5 text-gray-900)이 반복됩니다. not-found.tsx는 wrapping <div> 없이 Link만 전달하고, 나머지 두 파일은 <div className="flex items-center gap-3">으로 감싸고 있어 구조 불일치도 있습니다.

공통 StatusActionButton 컴포넌트를 추출하면 중복 제거와 일관성 확보를 동시에 잡을 수 있습니다.

♻️ 공통 StatusActionButton 컴포넌트 추출 제안
// components/status-screen/StatusActionButton.tsx
import { Link } from "`@/i18n/navigation`";

interface StatusActionButtonProps {
  label: string;
  href?: string;
}

export const StatusActionButton = ({
  label,
  href = "/",
}: StatusActionButtonProps) => (
  <Link
    href={href}
    className="typo-headline-m-16 rounded-[8px] border border-gray-500 px-4 py-2.5 text-gray-900"
  >
    {label}
  </Link>
);

각 파일에서의 사용:

- action={
-   <div className="flex items-center gap-3">
-     <Link
-       href="/"
-       className="typo-headline-m-16 rounded-[8px] border border-gray-500 px-4 py-2.5 text-gray-900"
-     >
-       {t("homeButton")}
-     </Link>
-   </div>
- }
+ action={<StatusActionButton label={t("homeButton")} />}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/timo-web/app/global-error.tsx` around lines 50 - 58, 중복된 상태 화면 액션
버튼(Link) 구현을 공통 컴포넌트로 추출하세요. global-error.tsx의 action, error.tsx의 action,
not-found.tsx의 버튼이 동일한 스타일과 거의 같은 구조를 반복하므로, StatusActionButton 같은 재사용 컴포넌트를 만들어
href와 label만 받도록 정리하고 세 파일에서 이를 사용하세요. 또한 global-error.tsx와 error.tsx의 wrapping
div와 not-found.tsx의 단일 Link 구조 차이를 함께 정리해, 각 화면의 action 렌더링 형태가 일관되게 맞도록 수정하세요.

Comment on lines +22 to +33
useEffect(() => {
const controller = new AbortController();

fetch(src, { signal: controller.signal })
.then((response) => response.json())
.then(setAnimationData)
.catch(() => {
// 애니메이션 로드 실패 시 컨테이너만 비운 상태로 유지 (레이아웃 시프트 방지)
});

return () => controller.abort();
}, [src]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# lottie-react v2.4.1의 Lottie 컴포넌트가 animationData, loop, role, aria-label props를 지원하는지 확인
rg -n "animationData|aria-label|role" node_modules/lottie-react/dist/*.d.ts 2>/dev/null || echo "Type definitions not found at expected path"
fd "lottie-react" node_modules --type d --max-depth 3

Repository: Team-Timo/Timo-client

Length of output: 203


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file =="
sed -n '1,220p' apps/timo-web/components/lottie/LottiePlayer.tsx

echo
echo "== lottie-react references =="
rg -n "from 'lottie-react'|from \"lottie-react\"|<Lottie" apps/timo-web -S

echo
echo "== package refs =="
rg -n "\"lottie-react\"|lottie-react" package.json apps/timo-web/package.json pnpm-lock.yaml package-lock.json yarn.lock 2>/dev/null || true

Repository: Team-Timo/Timo-client

Length of output: 1949


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== LottiePlayer.tsx =="
cat -n apps/timo-web/components/lottie/LottiePlayer.tsx | sed -n '1,220p'

echo
echo "== search for response.ok around fetches =="
rg -n "response\.ok|fetch\(" apps/timo-web -S

Repository: Team-Timo/Timo-client

Length of output: 1680


🌐 Web query:

lottie-react animationData prop invalid object behavior response.ok fetch JSON 404

💡 Result:

In lottie-react, the animationData prop expects a parsed JSON object [1][2]. It does not support passing a URL string directly [3][1]. If you are fetching your animation data, you must perform the fetch operation manually and pass the resulting JSON object to the component [1]. When fetching animation data, it is critical to handle HTTP errors correctly because the Fetch API does not automatically throw an error for status codes like 404 or 500 [4]. Behavior with invalid objects or 404s: 1. Fetch API 404 Behavior: If a request results in a 404 status, the fetch promise will still resolve as successful [4]. If you blindly call response.json on a 404 response, the parser will likely fail with a SyntaxError because it will attempt to parse the server's HTML error page (which usually starts with <) as JSON [4]. 2. Invalid animationData: If you pass an invalid object (e.g., malformed JSON, an empty object, or a string instead of an object) to the animationData prop, the Lottie player will typically fail to initialize the animation, often resulting in no output or silent failures [1][5]. Recommended Pattern: Always check the response.ok property before attempting to parse the response [4][6]. const fetchAnimation = async (url) => { const response = await fetch(url); if (!response.ok) { throw new Error(Failed to load animation: ${response.status} ${response.statusText}); } const data = await response.json; return data; }; In your React component, use state to handle the asynchronous loading of this data, and conditionally render the Lottie component only after the data has been successfully fetched and parsed [1]. If the fetch fails, you should catch the error and handle it in your state to avoid passing undefined or invalid data to the Lottie component [1].

Citations:


response.ok를 먼저 확인하세요.
fetch()는 404/500에서도 성공으로 resolve되므로, 비정상 응답까지 json()으로 넘기면 잘못된 animationData<Lottie>에 들어갈 수 있습니다. response.ok가 아니면 예외를 던져 현재 catch로 보내는 게 안전합니다.
MDN: Response.ok, fetch()

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/timo-web/components/lottie/LottiePlayer.tsx` around lines 22 - 33, In
LottiePlayer’s fetch effect, the current response handling passes every resolved
fetch directly to response.json(), which allows 404/500 responses to be treated
as valid animation data. Update the useEffect flow to check response.ok before
parsing, and throw an error when it is false so the failure is routed into the
existing catch path. Keep the fix localized to the fetch chain in LottiePlayer
so only valid animation JSON reaches setAnimationData.

Comment thread apps/timo-web/messages/ko.json
@kimminna kimminna changed the title [FEAT] 에러/로딩 상태 페이지 Lottie 애니메이션 적용 [FEAT] 에러/로딩/온보딩 상태 페이지 Lottie 애니메이션 적용 Jul 9, 2026

@ehye1 ehye1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

로띠 파일 어떻게 넣는지 궁금했는데 pr설명이랑 코드 보고 배워갑니다💥
수고하셨습니다!!!💨🫡

useEffect(() => {
const controller = new AbortController();

fetch(src, { signal: controller.signal })

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lottie json 파일을 어떻게 가져오나 궁금했는데 fetch로 가져오게 되면 필요할 때만 가져올 수 있어서 번들 크기를 줄일 수 있겠네요!

.then((response) => response.json())
.then(setAnimationData)
.catch(() => {
// 애니메이션 로드 실패 시 컨테이너만 비운 상태로 유지 (레이아웃 시프트 방지)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lottie 파일이 렌더링 되기 전까지 CLS가 발생하는 것 같은데 나중에 높이 고정해놓거나 placeholder 설정해두면 좋을 것 같아요!

@jjangminii jjangminii left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이제 500,404에러도 이쁘겠네요~
간단한 코멘트라 어푸 먼저 해드릴게요-! 코멘트 내용 확인해주세요!

},
"Error": {
"title": "잠시 문제가 발생했어요.",
"description": "더 나은 경험을 위해 잠시 점검 중입니다. 잠시후 다시 이용해 주새요.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ㅎㅎㅎㅎㅎ 오타 확인해주세요~

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

허걱~~~

export default function Loading() {
return (
<div className="flex min-h-screen w-full items-center justify-center">
<LottiePlayer src="/lottie/loading.json" className="w-[143px]" />

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기 ariaLabel 누락 되었어요~

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

추가했습니다!

Comment on lines +25 to +29
fetch(src, { signal: controller.signal })
.then((response) => response.json())
.then(setAnimationData)
.catch(() => {
// 애니메이션 로드 실패 시 컨테이너만 비운 상태로 유지 (레이아웃 시프트 방지)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fetch(src, { signal: controller.signal })
.then((response) => response.json())
.then(setAnimationData)
.catch(() => {
// 애니메이션 로드 실패 시 컨테이너만 비운 상태로 유지 (레이아웃 시프트 방지)
fetch(src, { signal: controller.signal })
.then((response) => {
if (!response.ok) throw new Error(`Failed to load: ${response.status}`);
return response.json();
})
.then(setAnimationData)
.catch(() => {
// 애니메이션 로드 실패 시 컨테이너만 비운 상태로 유지 (레이아웃 시프트 방지)
});

fetch는 404/500에서도 성공으로 resolve되기 때문에, response.ok 체크 없이 바로 .json()을 호출하면 에러 페이지의 HTML이 animationData로 들어가서 Lottie가 깨질 수 있을 것 같아요!
response.ok가 false일 때 에러를 throw하면 기존 .catch(() => {}) 블록으로 떨어져서 animationData가 null인 상태로 유지되고, Lottie 컴포넌트가 렌더링되지 않아요. 기존 catch 로직을 재활용하면서 최소한의 변경으로 방어할 수 있을 것 같아요!
민아님은 어떻게 생각하시나요?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 새로운 기능(기능성) 구현 ⏰ Timo-web Timo 웹 서비스 ♦️ 민아 민아상

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] 에러/로딩 상태 페이지 Lottie 애니메이션 적용

3 participants