Notice
Recent Posts
Recent Comments
Link
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 |
Tags
- 공부시간측정어플
- 동적계획법
- react
- 포도주시식
- nodejs
- 자료구조
- 캠스터디
- discord.js
- 크롬 익스텐션
- X
- 백준 #7568번 #파이썬 #동적계획법
- TypeScript
- supabase
- 갓생
- popup
- Chrome Extension
- webpack
- background script
- content script
- 파이썬
- 프로그래머스 #정수삼각형 #동적계획법
- 디스코드 봇
- 2156
- 백준 7579
- 크롬 확장자
- 백준
- Message Passing
- C언어로 쉽게 풀어쓴 자료구조
Archives
- Today
- Total
히치키치
다국어 서비스에서 실용적인 사용자 중심 에러 처리 본문
완벽한 에러 처리보다 사용자가 길을 잃지 않는 것이 더 중요하다
배경
Next.js 14 App Router와 next-intl을 사용해 다국어 서비스 개발 중, 사용자가 잘못된 URL에 접근하거나 지원하지 않는 언어로 접속할 때의 처리 방법에 대해 고민함
다양한 잘못된 접근 패턴들
- 존재 하지 않는 페이지 접근 : /invalid-page, /random/path 등
- 지원하지 않는 locale 접근 : /fr/home
- 잘못된 경로 구조 : /ko/invalid/nested/path
- ChunkLoadError, PermissionError, RuntimeError 등
핵심 아이디어
- 복잡한 에러 분류에 대한 각각의 해결 방안 고안 X
- 해당 에러를 마주한 사용자의 위치를 서비스 진입 경로로 재연결시켜 서비스 이용 여정을 이어나가게 함
- 개발 리소스 대비 최대 효과 추구
- 짧은 시간 구현으로 사용자 이탈을 방지하고, 서비스 핵심 플로우로 자연스럽게 유도하는 것
비즈니스 로직과 사용자 상태 기반 복구 전략
- 미인증 사용자 → 로그인 페이지 (서비스 진입점으로 유도)
- 인증된 사용자 → 메인 서비스 (핵심 기능(/cube 페이지)으로 복귀)
Nextjs App router 폴더 구조 이해
app/
├── [locale]/
│ ├── layout.tsx # locale 검증
│ ├── page.tsx # 루트 페이지
│ ├── not-found.tsx # 404 페이지
│ └── [...rest]/ # catch-all
│ └── page.tsx
├── error.tsx # 에러 바운더리
└── global-error.tsx # 전역 에러 처리
1. 파일 기반 라우팅 시스템
- app/[locale]/ → 다국어 동적 라우팅
- app/[locale]/page.tsx → 각 locale의 루트 페이지
- app/[locale]/cube/page.tsx → 각 locale의 주요 서비스 페이지
- app/[locale]/[...rest]/ → catch-all 라우팅 (모든 하위 경로 포착)
2. 에러 처리 계층 구조
- global-error.tsx → 최상위 전역 에러 (앱 전체)
- error.tsx → 루트 레벨 에러
- [locale]/layout.tsx → locale 레벨 검증
- not-found.tsx → 404 전용 처리
3. 라우팅 우선 순위
- [locale]/page.tsx → 정확한 매칭 우선
- [locale]/cube/page.tsx → 정적 경로 우선
- [locale]/[...rest]/page.tsx → 마지막 catch-all
4. 실제 서비스 flow
[정상적 요청인 경우]
1. middleware.ts → locale 검증
2. [locale]/layout.tsx → locale 재검증
3. 매칭되는 page.tsx 제공됨
[잘못된 URL 접근인 경우]
1. middleware.ts (통과)
2. [locale]/layout.tsx (통과)
3. 매칭되는 page.tsx 없음
4. [...rest]/page.tsx 실행 → 인증 상태별 분기
5. not-found.tsx → 복구 메커니즘 제공
4단계 방어선 구축
1. 1단계: Middleware
- 역할: 잘못된 언어 접근 자체 봉쇄
// middleware.ts - 지원하지 않는 locale 필터링
export default createMiddleware(routing);
export const config = {
matcher: "/((?!api|trpc|_next|_vercel|.*\\..*).*)",
};
2. 2단계: 구조적 검증 (Layout)
- 역할: 1단계 통과한 edge case까지 포착 + 라이브러리 무관 검증 로직 커스텀 가능
// layout.tsx - locale 유효성 재검증
if (!hasLocale(routing.locales, locale)) {
notFound();
}
3. 3단계: 지능형 라우팅 (Catch-All)
- 역할: 모든 미매칭 경로를 의미있는 목적지로 전환
// [...rest]/page.tsx - 핵심 구현
export default async function CatchAllPage({ params }: { params: Params }) {
const access_token = cookieStore.get("access_token")?.value ?? null;
const { locale } = await params;
// 🎯 핵심 로직: 인증 상태 기반 복구
if (!access_token) {
// 미인증 사용자 → 로그인으로 유도
redirect({ href: pageUrl.signin, locale });
} else {
// 인증된 사용자 → 404 페이지 (자동 복구 및 메인 기능 복구 로직이 담긴 페이지)
return notFound();
}
}
4. 최후 방어 (Error Boundary)
- 역할: 잘못된 url 입력으로 인한 런타임 에러 상황을 안전하게 처리
// error.tsx/global-error.tsx
export default function Error({ error, reset }: ErrorProps) {
useEffect(() => {
logger({
eventType: EventName.RUNTIME_ERROR,
// ... 상세 에러 정보 수집
});
}, [error]);
return notFound(); // 통합된 복구 메커니즘으로 연결
}
운영 데이터를 위한 로깅 시스템
- 에러 발생 상황을 운영 개선의 기회으로 활용
- 자명한 에러 발생 패턴 파악의 필요성
- 에러 발생 시점과 컨텍스트 정보 수집
- 에러 발생 URL 패턴 및 사용자 접근 경로
- 디바이스/브라우저 정보 → 호환성 이슈 파악
- 에러 발생 빈도 → 시스템 안정성 모니터링
// 일부 예시!
await logger({
eventType: EventName.NOT_FOUND,
userAgent: headerList.get("user-agent"),
request: {
url: fullUrl,
method: ApiMethod.get,
},
error: {
name: EventName.NOT_FOUND,
message: `No matched Page under [locale]`,
stack: `apps/web/app/[locale]/[...rest]/page.tsx`,
source: fullUrl,
},
});
능동적인 서비스 이용 여정 복구 로직
사용자 심리
- 기술적 완성도보다 사용자가 서비스를 계속 이용할 수 있는지에 집중
- 어떤 에러가 발생했는지는 사실 관심없고 그래서 어캐하면 계속 할 수 있는지가 중요함
- "어? 뭔일이지? 길을 잃었네" → 시스템 응답: "괜찮아요, 이쪽으로 안내할게요"
- 사용자가 본인을 능동적으로 구출하는 로직 필요함
복구 메커니즘
- 즉시 복구: "홈으로 가기" 버튼
- 자동 복구: 10초 카운트다운 후 자동 이동
- 친화적 안내: 딱딱한 에러가 아닌 도움말 제공
- 유지보수: 복구 관련 로직이 응집되어 있어 추후 상황에 맞게 조절 및 작성 용이
// not-found.tsx - 핵심 복구 메커니즘
export default function NotFoundPage() {
const [countdown, setCountdown] = useState(10);
const router = useRouter();
useEffect(() => {
// 10초 후 자동 홈페이지 이동
const timer = setInterval(() => {
setCountdown((prev) => {
if (prev <= 1) {
router.push("/cube"); // 메인 서비스로 복구
return 0;
}
return prev - 1;
});
}, 1000);
return () => clearInterval(timer);
}, [router]);
return (
<div>
{/* 친근한 404 UI */}
<div className="text-6xl font-bold text-gray-300">404</div>
{/* 즉시 복구 옵션 */}
<Button onClick={() => router.push(pageUrl.cube)}>
홈으로 가기
</Button>
{/* 자동 복구 안내 */}
{countdown > 0 && (
<p>{countdown}초 후 홈페이지로 자동 이동됩니다.</p>
)}
</div>
);
}
효과
1. 개발 효율성
- 30분 구현으로 99% 에러 케이스 해결
- 복잡한 에러 분류 대신 명확한 비즈니스 로직 적용
- 유지보수하기 쉬운 단순한 구조
2. 사용자 경험 개선
- before: 에러 페이지 막다른 길
- after: 자동/수동 복구 옵션이 담겨 있어 서비스 지속적 이용 가능
3. 비지니스 임팩트
- 사용자 이탈률 감소: 길을 잃고 브라우저 창 꺼버리는 것 방지
- 서비스 재진입 유도: 로그인 → 메인 기능으로 자연스러운 플로우
- 장애 상황 최소화: 예상치 못한 URL 접근도 안전하게 처리
'Blockie' 카테고리의 다른 글
| Next.js 실행환경 기반 규모에 맞는 현실적인 로깅 시스템 구축기 (2) | 2025.08.18 |
|---|---|
| 지출 블록 시각화에서 데이터 정합성 문제 해결기 (8) | 2025.07.27 |
| 배포 최적화 및 에러 대응 (2) | 2025.07.20 |
| 모노레포 의존성 빌드 지옥에서 살아남기 (1) | 2025.07.19 |
| 사용자 인증이 필요한 페이지를 어떻게 빠르고 안전하게 렌더링할까? (7) | 2025.07.12 |
Comments