시니어 숲 프로젝트의 핵심 기능인 '내 요양등급 찾기' 모의평가 서비스를 기획·설계·구현한 과정을 기술적으로 정리합니다.
1. 왜 만들었나
노인장기요양보험의 등급 판정은 국민건강보험공단에서 직접 받아야 하지만, 신청 전에 "우리 부모님이 대략 몇 등급에 해당할까?"를 미리 가늠해보고 싶은 수요가 많습니다. 기존에는 이를 간단히 확인할 수 있는 웹 기반 도구가 부족했고, 있더라도 모바일 친화적이지 않거나 최신 수가 기준이 반영되지 않은 경우가 대부분이었습니다.
시니어 숲에서는 보건복지부 고시 52개 평가 항목을 기반으로, 5분 안에 예상 등급과 2026년 기준 지원 혜택을 미리 확인할 수 있는 웹 모의평가 도구를 개발했습니다.
2. 기술 스택 한눈에 보기
| 영역 | 선택 기술 | 이유 |
|---|---|---|
| 프레임워크 | Next.js (App Router) | SSR/SSG 혼합, SEO 최적화에 유리 |
| 상태관리 | Zustand | 52개 문항 답변 + 스텝 내비게이션을 가볍게 관리 |
| 애니메이션 | Framer Motion | 스텝 전환, 카드 등장, 결과 임팩트 연출 |
| 아이콘 | Lucide React | 경량 + 트리쉐이킹 가능 |
| 스타일링 | Tailwind CSS | 다크/라이트 모드 대응, 반응형 UI |
| 질문 데이터 | JSON 정적 파일 | 서버 없이 빌드 타임에 임포트 가능 |
3. 전체 아키텍처
[JSON 문항 데이터] → longtermCareData.ts (타입 정의 + 5단계 스텝 매핑)
↓
assessmentStore.ts (Zustand 스토어)
↓ ↓
AssessmentWizard.tsx calculateScore.ts
├── StepProgressBar (채점 엔진)
├── QuestionCard ↓
└── ResultView ←──────── 등급·혜택·본인부담금
핵심 설계 원칙
- 데이터와 UI의 완전 분리: JSON 문항 파일은 순수 데이터만 담고, 스텝 구성·옵션 매핑 등은
longtermCareData.ts에서 처리합니다. - 단방향 데이터 흐름: 사용자 입력 → Zustand 스토어 → UI 렌더링 → 계산 엔진 호출 → 결과 표시.
- 점진적 검증: 각 스텝을 넘어갈 때 미응답 문항을 즉시 검출하고 해당 위치로 자동 스크롤합니다.
4. 주요 구현 포인트
4.1 Wizard 패턴 — 52문항을 5단계로 분할
52개 문항을 한 페이지에 전부 나열하면 사용자 이탈률이 급증합니다. 이를 방지하기 위해 5단계 Wizard 패턴을 적용했습니다.
// longtermCareData.ts
export interface WizardStep {
stepIndex: number;
stepName: string;
stepIcon: string;
sections: {
categoryId: string;
categoryName: string;
options: AssessmentOption[];
items: AssessmentItem[];
}[];
}
하나의 스텝에 여러 섹션(sections)을 담을 수 있도록 설계한 것이 핵심입니다. 예를 들어 '재활' 카테고리는 '팔다리 움직임'(4문항)과 '관절 굳음'(6문항) 두 하위 섹션이 각각 다른 옵션 세트를 가지지만, 사용자에게는 하나의 스텝으로 보여줍니다.
4.2 Zustand를 활용한 상태 관리
// assessmentStore.ts (핵심 구조)
interface AssessmentState {
currentStep: number;
answers: Record<string, number>; // { "p01": 1, "c03": 0, ... }
result: AssessmentResult | null;
showResult: boolean;
invalidItems: string[];
setAnswer: (id: string, score: number) => void;
nextStep: () => boolean;
validateCurrentStep: () => string[];
calculateResult: () => void;
}
answers를 Record<string, number> 형태로 관리하여 52개 문항의 ID와 점수를 flat하게 저장합니다. 이 구조 덕분에 채점 엔진에서 answersRecord["p01"]처럼 직관적으로 접근할 수 있습니다.
4.3 미응답 검증 및 UX 처리
사용자가 '다음 단계' 버튼을 누르면 현재 스텝의 모든 문항이 응답되었는지 검증합니다.
validateCurrentStep: () => {
const step = WIZARD_STEPS[currentStep];
const missing: string[] = [];
for (const section of step.sections) {
for (const item of section.items) {
if (answers[item.id] === undefined) {
missing.push(item.id);
}
}
}
set({ invalidItems: missing });
return missing;
}
미응답 문항이 있으면:
- 해당 카드에 빨간 테두리 + 경고 메시지 표시
- 첫 번째 미응답 문항으로 자동 스크롤 이동
- 버튼 상태는 비활성화하지 않음 (사용자가 혼란 없이 다시 시도 가능)
4.4 가이드 툴팁 — 문항별 상세 안내
각 문항에는 선택적으로 guide 필드가 포함될 수 있습니다. 이를 활용해 문항 텍스트 옆에 ⓘ 아이콘을 배치하고, 클릭 시 Framer Motion 애니메이션과 함께 평가 기준이나 주의사항을 펼쳐 보여줍니다.
// QuestionCard.tsx (간소화)
<div className="flex items-center gap-2">
<p className="text-base font-bold">{item.question}</p>
{item.guide && (
<button onClick={() => setShowGuide(!showGuide)}>
<Info size={20} />
</button>
)}
</div>
<AnimatePresence>
{showGuide && item.guide && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }}
>
💡 <strong>참고 안내:</strong> {item.guide}
</motion.div>
)}
</AnimatePresence>
4.5 채점 엔진 — 비례 기반 휴리스틱 모델
실제 건강보험공단의 등급 판정은 수형분석(Tree Regression) 모델을 사용하지만, 이는 비공개입니다. 따라서 본 서비스에서는 사용자 응답에 비례하여 8개 서비스군 점수를 산출하는 휴리스틱 모델을 적용했습니다.
사용자 응답(52개 문항)
↓ extractAnswers()
5개 영역별 원점수 집계 (신체·인지·행동·간호·재활)
↓
8개 서비스군 점수로 비례 환산
↓ 합산 = 요양인정점수
등급 판정 (1~5등급 / 인지지원등급 / 등급외)
등급 판정 기준:
| 점수 범위 | 등급 |
|---|---|
| 95점 이상 | 1등급 |
| 75~94점 | 2등급 |
| 60~74점 | 3등급 |
| 51~59점 | 4등급 |
| 45~50점 | 5등급 |
| 45점 미만 | 등급외 (정상) |
4.6 결과 화면 — 한도액 카드 + 본인부담금 시뮬레이터
결과 화면에서는 등급에 따른 2026년 기준 재가급여 월 한도액과 시설급여 1일 한도액을 카드 형태로 표시합니다. 추가로 소득 수준에 따른 본인부담금 시뮬레이터를 제공하여, 일반(15%)/감경(9%·6%)/면제(0%) 4가지 케이스의 실제 부담금을 실시간으로 확인할 수 있습니다.
5. 인트로 페이지 & 라우트 설계
/assessment → 인트로 랜딩 페이지 (SSG)
/assessment/questions → 52문항 Wizard (CSR)
인트로 페이지는 정적 생성(Static Generation)으로 빌드하여 초기 로딩 속도를 극대화하고, SEO에 유리한 메타데이터를 서버 사이드에서 주입합니다. 실제 문항 풀이 화면은 클라이언트 사이드에서 Zustand 상태 관리와 함께 동작합니다.
6. SEO 최적화 전략
6.1 Next.js Metadata API
export const metadata: Metadata = {
title: '장기요양등급 자가진단 모의평가 | 시니어 숲',
description: '5분 만에 우리 부모님의 예상 장기요양등급과 ...',
keywords: ['장기요양등급', '장기요양등급 자가진단', ...],
openGraph: { ... },
twitter: { card: 'summary_large_image', ... },
};
6.2 JSON-LD 구조화 데이터
AI 검색 결과(SGE) 및 리치 스니펫 노출을 위해 WebApplication 스키마를 삽입했습니다.
const jsonLd = {
"@context": "https://schema.org",
"@type": "WebApplication",
"name": "시니어 숲 장기요양등급 자가진단",
"applicationCategory": "HealthApplication",
"offers": { "@type": "Offer", "price": "0", "priceCurrency": "KRW" }
};
6.3 Sitemap 등록
src/app/sitemap.ts에서 /assessment 경로를 priority 0.9로 명시적으로 등록하여 크롤러가 누락 없이 수집하도록 했습니다.
7. 반응형 & 접근성
모바일 헤더 네비게이션
기존에 화면 하단에 고정되던 탭 바(Bottom Tab Bar)가 푸터나 CTA 버튼을 가리는 문제가 있었습니다. 이를 해결하기 위해 하단 탭 바를 완전히 제거하고, 상단 모바일 헤더에 압축된 아이콘 버튼 형태(기관, 등급)로 이동시켰습니다.
┌─────────────────────────────┐
│ 🌲 시니어숲 📍기관 📋등급 ☀️ ☰ │ ← 모바일 헤더
├─────────────────────────────┤
│ │
│ 컨텐츠 영역 (100vh 확보) │
│ │
├─────────────────────────────┤
│ 푸터 (가려짐 없음) │
└─────────────────────────────┘
네비게이션 활성 상태 — 하위 경로 대응
// 기존: 정확 일치만 활성화 → /assessment/questions 시 비활성
const isActive = pathname === item.href;
// 개선: 하위 경로까지 활성 상태 유지
const isActive = item.href === "/"
? pathname === "/"
: pathname.startsWith(item.href);
홈(/)은 정확 일치, 나머지는 접두사 매칭을 사용하여 /assessment/questions에서도 '등급찾기' 메뉴가 활성 상태를 유지합니다.
8. 배운 점 & 다음 단계
배운 점
- Wizard 패턴은 긴 설문에 필수: 52문항을 5단계로 나누니 완주율이 체감상 크게 향상됐습니다.
- Zustand의 flat한 Record 구조: 복잡한 nested state 없이도 52개 문항을 깔끔하게 관리할 수 있었습니다.
- SEO는 기능 완성 후가 아닌 설계 단계부터: Next.js의 Metadata API와 JSON-LD를 초기부터 적용하면 나중에 리워크할 필요가 없습니다.
향후 개선 방향
- 수형분석(Tree Regression) 정밀 모델 데이터 확보 시 채점 엔진 고도화
- 결과 페이지 공유 기능 (카카오톡, URL 복사)
- 결과 기반 주변 요양기관 추천 연동
면책 조항
본 모의평가는 참고용이며, 실제 국민건강보험공단의 장기요양등급 판정 결과와는 다를 수 있습니다.
'사이드프로젝트' 카테고리의 다른 글
| Porkbun에서 도메인 구매 및 구글 서치콘솔 주소 변경 가이드 (0) | 2026.03.11 |
|---|---|
| [지도 기반 서비스] 검색 UX와 렌더링 성능 최적화 경험기 (0) | 2026.02.25 |
| [개선] 장기요양시설 비교 기능 개선기 — 데이터, UX, 공유까지 (0) | 2026.02.20 |
| [개선] 모바일 뷰포트 수정과 직관적인 지도 검색 (0) | 2026.02.18 |
| 커뮤니티 기능 개발 전 네이버 블로그 RSS & API로 연동하기 (0) | 2026.02.12 |