Parent
#918 — 🗺️ [wayfinder] Error page 준수 (결정 D1·D2·D3·D7 / 리스크 R1·R2)
What to build
현재 (shell) 트리에는 세그먼트 에러 경계가 하나도 없다. 그래서 shell 페이지가 throw하거나 notFound()를 부르면 루트 경계까지 튀어 AppShell 전체가 언마운트되고, 동시에 ConditionalNav가 shell 경로에서 자기억제하기 때문에 사이드바도 모바일 바도 없는 헐벗은 중앙 블록이 남는다. 사용자에게 남는 탈출구는 본문 안의 링크 하나뿐이다.
(shell) 세그먼트에 에러 경계와 not-found 경계를 두어, 500과 "엔티티 없음" 404가 AppShell 안에서 렌더되게 한다. 사이드바·모바일 상/하단 바가 살아 있어 사용자가 곧바로 다른 데로 이동할 수 있어야 한다.
두 경계 모두 본문은 shared/ErrorState·EmptyState 조합으로 만든다(#1055에서 보강한 것). hand-roll 금지 — 이 둘이 첫 소비자가 된다.
스코프 경계 (결정 D2에서 확정): 이번에 잡는 404는 페이지가 명시적으로 notFound()를 호출한 경우뿐이다. Next는 매칭되는 라우트 자체가 없는 URL(/ko/asdf)의 404를 nested not-found로 보내지 않고 무조건 루트로 보낸다. 이를 셸 안으로 끌어오려면 [...rest] catch-all shim이 필요한데, 향후 신규 라우트를 조용히 삼킬 위험이 있고 locale prefix 없는 /asdf는 그걸로도 못 잡아 어차피 통일이 안 된다 → 채택하지 않는다. 오타 URL은 루트 브랜드 404에 그대로 착지한다.
not-found 문구는 (shell) 공용 1개로 시작한다(D3). posts·items·artists·brands·profile의 notFound()를 전부 받으므로 일반형 문구 + 탐색 유도 CTA. 엔티티별 카피가 필요해지면 나중에 해당 라우트가 자기 not-found로 오버라이드하면 된다.
decoded/[id]는 실재하지 않는다(index 페이지뿐) — #921 본문의 오기이므로 대상에서 뺀다. cody/looks/[slug]는 (shell) 밖이라 이번 스코프가 아니다(D5).
Acceptance criteria
Blocked by
Parent
#918 — 🗺️ [wayfinder] Error page 준수 (결정 D1·D2·D3·D7 / 리스크 R1·R2)
What to build
현재
(shell)트리에는 세그먼트 에러 경계가 하나도 없다. 그래서 shell 페이지가 throw하거나notFound()를 부르면 루트 경계까지 튀어 AppShell 전체가 언마운트되고, 동시에ConditionalNav가 shell 경로에서 자기억제하기 때문에 사이드바도 모바일 바도 없는 헐벗은 중앙 블록이 남는다. 사용자에게 남는 탈출구는 본문 안의 링크 하나뿐이다.(shell)세그먼트에 에러 경계와 not-found 경계를 두어, 500과 "엔티티 없음" 404가 AppShell 안에서 렌더되게 한다. 사이드바·모바일 상/하단 바가 살아 있어 사용자가 곧바로 다른 데로 이동할 수 있어야 한다.두 경계 모두 본문은
shared/ErrorState·EmptyState조합으로 만든다(#1055에서 보강한 것). hand-roll 금지 — 이 둘이 첫 소비자가 된다.스코프 경계 (결정 D2에서 확정): 이번에 잡는 404는 페이지가 명시적으로
notFound()를 호출한 경우뿐이다. Next는 매칭되는 라우트 자체가 없는 URL(/ko/asdf)의 404를 nested not-found로 보내지 않고 무조건 루트로 보낸다. 이를 셸 안으로 끌어오려면[...rest]catch-all shim이 필요한데, 향후 신규 라우트를 조용히 삼킬 위험이 있고 locale prefix 없는/asdf는 그걸로도 못 잡아 어차피 통일이 안 된다 → 채택하지 않는다. 오타 URL은 루트 브랜드 404에 그대로 착지한다.not-found 문구는
(shell)공용 1개로 시작한다(D3). posts·items·artists·brands·profile의notFound()를 전부 받으므로 일반형 문구 + 탐색 유도 CTA. 엔티티별 카피가 필요해지면 나중에 해당 라우트가 자기 not-found로 오버라이드하면 된다.decoded/[id]는 실재하지 않는다(index 페이지뿐) — #921 본문의 오기이므로 대상에서 뺀다.cody/looks/[slug]는(shell)밖이라 이번 스코프가 아니다(D5).Acceptance criteria
notFound()를 호출했을 때(/ko/posts/<없는id>) 마찬가지로 AppShell이 유지된다reset()재시도와 홈 이동을 둘 다 할 수 있다shared/ErrorState/EmptyState를 조합해 만든다 (hand-roll 금지, 하드코딩 hex 0)error.tsx와 동등한 수준)@modalparallel route와의 상호작용을 실제로 확인한다. 루트not-found.tsx주석에 "@modal + auto not-found →React.Children.only" 회귀 이력이 남아 있고@modal슬롯이app/@modal·app/[locale]/@modal양쪽에 존재한다. dev·프로덕션 빌드 양쪽에서 shell throw와notFound()를 재현해 이 에러가 나지 않음을 확인 — 재발하면 슬롯default.tsx보강으로 해소하고, 무엇이 필요했는지 이슈에 기록한다bun run build로(shell)세그먼트가 dynamic으로 떨어지지 않았는지 확인한다 (#942의setRequestLocale정적 렌더 규율 회귀 금지)/ko/asdf)은 여전히 루트 404로 간다 — D2가 의도한 동작이며 회귀가 아님을 확인Blocked by