11 KiB
레이아웃 JSON - 에러 핸들링
메인 문서: layout-json-features.md 관련 문서: 액션 핸들러 - 에러 핸들링 시스템 | 데이터 소스 - 에러 핸들링
에러 핸들링 설정 (errorHandling)
버전: engine-v1.6.0+
목적: 레이아웃 또는 템플릿 레벨에서 공통 에러 핸들링을 정의합니다. 개별 액션이나 데이터 소스에서 처리되지 않은 에러에 대한 폴백으로 동작합니다.
핵심 원칙
✅ 계층적 설정: 템플릿 → 레이아웃 → 액션/데이터소스 (우선순위 역순)
✅ 다양한 표시 방식: showErrorPage, toast, openModal, navigate
✅ 기존 액션 시스템 활용: sequence, parallel 지원
✅ 모든 HTTP 에러 코드 지원: 400, 401, 403, 404, 422, 429, 500, 503 등
우선순위
액션/데이터소스 errorHandling[코드] > errorHandling[default] > onError > 레이아웃 errorHandling > 템플릿 errorHandling > 시스템 기본값
레이아웃 레벨 설정
{
"version": "1.0.0",
"layout_name": "_admin_base",
"extends": null,
"errorHandling": {
"403": {
"handler": "toast",
"params": { "type": "warning", "message": "$t:errors.forbidden" }
},
"404": {
"handler": "showErrorPage",
"params": { "target": "content" }
},
"500": {
"handler": "showErrorPage",
"params": { "target": "content" }
}
},
"data_sources": [],
"components": []
}
템플릿 레벨 설정 (template.json)
템플릿의 template.json에서 전역 에러 핸들링을 정의할 수 있습니다.
{
"id": "sirsoft-admin_basic",
"name": "Admin Basic",
"errorHandling": {
"401": {
"handler": "navigate",
"params": { "path": "/admin/login" }
},
"403": {
"handler": "openModal",
"target": "forbidden_modal"
},
"404": {
"handler": "navigate",
"params": { "path": "/admin/404" }
},
"422": {
"handler": "toast",
"params": { "type": "error", "message": "{{error.message}}" }
},
"500": {
"handler": "navigate",
"params": { "path": "/admin/500" }
},
"default": {
"handler": "toast",
"params": { "type": "error", "message": "{{error.message}}" }
}
}
}
처리 흐름
API 에러 발생
↓
액션/데이터소스에 errorHandling 있음?
├── ✅ → 해당 설정으로 처리 (종료)
↓ ❌
레이아웃에 errorHandling 있음?
├── ✅ → 해당 설정으로 처리 (종료)
↓ ❌
템플릿에 errorHandling 있음?
├── ✅ → 해당 설정으로 처리 (종료)
↓ ❌
시스템 기본값 적용 (toast)
사용 가능한 핸들러
| 핸들러 | 설명 |
|---|---|
navigate |
페이지 이동 |
openModal |
모달 열기 |
toast |
토스트 알림 |
setState |
상태 변경 |
sequence |
순차 실행 |
parallel |
병렬 실행 |
showErrorPage |
에러 페이지 렌더링 |
sequence/parallel 지원
{
"errorHandling": {
"500": {
"handler": "parallel",
"actions": [
{
"handler": "toast",
"params": { "type": "error", "message": "$t:errors.server_error" }
},
{
"handler": "openModal",
"target": "error_report_modal"
}
]
}
}
}
에러 컨텍스트 변수
핸들러 params에서 사용 가능한 변수:
| 변수 | 설명 |
|---|---|
{{error.status}} |
HTTP 상태 코드 |
{{error.message}} |
에러 메시지 |
{{error.errors}} |
필드별 에러 (422) |
{{error.data}} |
전체 응답 데이터 |
시스템 기본값
errorHandling 설정이 어디에도 없을 경우 사용되는 기본값:
const DEFAULT_ERROR_HANDLING = {
401: {
handler: 'navigate',
params: { path: '{{auth.loginPath}}' }
},
403: {
handler: 'toast',
params: { type: 'error', message: '{{error.message}}' }
},
404: {
handler: 'toast',
params: { type: 'error', message: '{{error.message}}' }
},
422: {
handler: 'toast',
params: { type: 'error', message: '{{error.message}}' }
},
default: {
handler: 'toast',
params: { type: 'error', message: '{{error.message}}' }
}
};
레이아웃 상속 시 병합 규칙
errorHandling은 레이아웃 상속 시 다음 규칙으로 병합됩니다:
- 에러 코드 기반 병합: 동일한 에러 코드에 대해 자식 레이아웃이 부모를 오버라이드
- 추가: 부모에 없는 에러 코드는 자식에서 추가됨
- default 상속: 자식에 default가 없으면 부모의 default 사용
// 부모 레이아웃 (_admin_base.json)
{
"errorHandling": {
"403": { "handler": "toast", "params": { "message": "권한 없음" } },
"500": { "handler": "showErrorPage", "params": { "target": "full" } }
}
}
// 자식 레이아웃 (admin_user_list.json)
{
"extends": "_admin_base",
"errorHandling": {
"403": { "handler": "openModal", "target": "forbidden_modal" },
"404": { "handler": "toast", "params": { "message": "사용자를 찾을 수 없습니다" } }
}
}
// 병합 결과
{
"errorHandling": {
"403": { "handler": "openModal", "target": "forbidden_modal" }, // 자식으로 오버라이드
"404": { "handler": "toast", "params": { "message": "사용자를 찾을 수 없습니다" } }, // 자식에서 추가
"500": { "handler": "showErrorPage", "params": { "target": "full" } } // 부모에서 상속
}
}
fallback과의 관계
데이터 소스에 fallback과 errorHandling이 함께 정의된 경우:
✅ errorHandling이 먼저 실행됨 (toast, showErrorPage, navigate 등)
✅ 그 다음 fallback 데이터가 렌더링에 사용됨
fallback이 있어도 errorHandling은 건너뛰지 않음
에러 전파 방지 (suppress 핸들러)
@since engine-v1.21.0
특정 에러 코드가 상위 레벨(레이아웃/템플릿)로 전파되지 않도록 의도적으로 차단할 때 사용합니다.
대표 사례: 비회원의 /api/auth/user 401 응답은 정상 동작이므로, 레이아웃의 errorHandling.401(showErrorPage 등)로 전파되면 안 됩니다.
{
"id": "current_user",
"type": "api",
"endpoint": "/api/auth/user",
"auth_required": true,
"fallback": { "data": [] },
"errorHandling": {
"401": {
"comment": "비회원 401은 정상 — 전파 방지",
"handler": "suppress"
}
}
}
동작 원리:
suppress는ErrorHandlingResolver에서 truthy 핸들러로 인식 →level: 'action'으로 resolve- 상위 레벨(레이아웃/템플릿) errorHandling으로 전파되지 않음
ActionDispatcher에서 로그만 출력하는 no-op 핸들러로 실행
사용 가능 위치:
- 데이터소스
errorHandling apiCall핸들러의errorHandlingdefault키와 함께 사용 가능 (모든 에러 코드 전파 방지)
✅ 에러 전파 방지가 목적일 때 suppress 사용 (무의미한 setState 대신)
✅ fallback과 함께 사용 — suppress로 전파 방지 + fallback으로 기본값 렌더링
실제 에러 처리가 필요한 경우에는 suppress 대신 적절한 핸들러 사용
에러 페이지에서의 상태 초기화 (engine-v1.28.1+)
에러 페이지(404, 403, 500 등)에서도 initGlobal/initLocal 매핑이 정상 처리됩니다.
ErrorPageHandler.renderError()는 다음 순서로 동작합니다:
- error_config 로드 — 에러 코드별 레이아웃 매핑 정보
- 레이아웃 로드 —
LayoutLoader를 통한 에러 레이아웃 JSON 로드 (extends 병합 자동 처리) - data_sources fetch — blocking + progressive 데이터 소스를 모두 fetch (에러 페이지에서는 모든 데이터 로드 후 렌더링)
processInitOptions()호출 — fetch된 데이터를initGlobal/initLocal매핑에 따라_global/_local에 저장- 렌더링 —
errorCode+fetchedData+_global+_local을 dataContext로 전달하여 렌더링
이를 통해 에러 페이지에서도:
_global.currentUser등 인증 상태가 사용 가능- 에러 페이지 레이아웃에서 헤더/로그인 상태 표시가 정상 동작
initGlobal의 문자열/배열/객체({ key, path }) 형태 모두 지원
{
"data_sources": [
{
"id": "current_user",
"endpoint": "/api/auth/user",
"auth_required": true,
"fallback": { "data": [] },
"initGlobal": "currentUser",
"errorHandling": {
"401": { "handler": "suppress" }
}
}
]
}
참고:
processInitOptions()는TemplateApp.processInitOptions()의 간소화 버전으로, 에러 페이지에서 필요한 형태만 지원합니다. 데이터 fetch 실패 시에도 에러 페이지 렌더링은 중단되지 않습니다.
주의사항
✅ 레이아웃 레벨 errorHandling은 해당 레이아웃 내 모든 액션/데이터소스에 적용
✅ 템플릿 레벨 errorHandling은 해당 템플릿의 모든 레이아웃에 적용
✅ 개별 액션/데이터소스에서 오버라이드 가능
extends로 상속받은 레이아웃의 errorHandling도 병합됨
순환 참조 시 상위 레벨 errorHandling 무시됨
배열형 onSuccess/onError 패턴
데이터 소스 및 apiCall 핸들러의 onSuccess/onError에서 배열 형태로 여러 액션을 순차 실행할 수 있습니다. 이는 sequence 핸들러와 동일한 동작이며, $prev/$results 컨텍스트 변수를 지원합니다.
배열형 onSuccess
{
"handler": "apiCall",
"target": "/api/admin/settings",
"params": { "method": "POST", "body": "{{_local.formData}}" },
"onSuccess": [
{
"handler": "setState",
"params": {
"target": "local",
"isSaving": false,
"hasChanges": false
}
},
{
"handler": "toast",
"params": { "type": "success", "message": "$t:common.save_success" }
},
{
"handler": "refetchDataSource",
"params": { "dataSourceId": "settings" }
}
]
}
배열형 onError
{
"onError": [
{
"handler": "setState",
"params": {
"target": "local",
"isSaving": false,
"errors": "{{error.errors ?? {}}}"
}
},
{
"handler": "toast",
"params": { "type": "error", "message": "{{error.message}}" }
}
]
}
$prev/$results 지원
배열형 onSuccess/onError에서 이전 액션의 결과를 참조할 수 있습니다:
| 변수 | 설명 |
|---|---|
$prev |
직전 액션의 결과값 |
$results |
모든 이전 액션 결과 배열 |
✅ 배열형 onSuccess/onError는 내부적으로 sequence와 동일하게 처리
✅ 순차 실행: 첫 번째 → 두 번째 → ... 순서 보장
중간 액션이 실패하면 이후 액션은 실행되지 않음