블로그로 돌아가기
Frontend

최애의 관리자 — 성능·SEO·접근성 대규모 리팩토링

2026.03.16·7분 소요·1회 조회
S시리즈 · 1 / 2

최애의 관리자

1최애의 관리자 — 성능·SEO·접근성 대규모 리팩토링
2최애의 관리자 — 스토리 뷰어부터 플래너까지, 주요 기능 개발기

왜 리팩토링이 필요했나?

최애의 관리자(SAIGO NO DANTE)는 림버스 컴퍼니 팬 정보 사이트로, 수감자 인격·EGO 데이터베이스, 거울 던전 기프트 도감, 덱 빌더 등을 제공합니다. 기능이 늘어나면서 성능과 코드 품질 문제가 쌓이기 시작했고, 체계적인 감사를 통해 42건의 이슈를 발견하여 전면 리팩토링을 진행했습니다.

감사 프로세스

React 베스트 프랙티스, SEO, 접근성(WCAG AA) 3가지 관점에서 동시에 감사를 진행했습니다.

발견된 이슈

심각도건수주요 내용
CRITICAL2건배럴 import로 서버 코드 번들 포함, Image sizes 미지정
HIGH12건React.memo 미사용, generateMetadata 없음, 모달 접근성
MEDIUM16건Observer 과다 재생성, 색상 대비 미달, 인라인 DOM 조작
LOW12건상수 위치, CSS 인라인, hreflang 미설정

Chunk 1: 번들 크기 최적화 (CRITICAL)

배럴 Import 리팩토링

가장 심각한 문제는 lib/data/sinners.ts라는 배럴 파일이었습니다. 이 파일이 6개 모듈을 export *로 재수출하면서, 서버 전용 DB 쿼리 코드(lib/db/queries.ts)까지 클라이언트 번들에 포함시키고 있었습니다.

typescripttypescript
// Before: 서버 코드가 클라이언트에 노출
export * from '@/lib/db/queries'  // getSinners, getIdentities 등 서버 전용

46개 파일의 import 경로를 개별 모듈로 변경했습니다:

typescripttypescript
// After: 필요한 것만 직접 import
import type { Identity } from '@/lib/types'
import { STORAGE_BASE } from '@/lib/constants/game'
import { getSinnerLogoUrl } from '@/lib/utils/images'

Chunk 2: 이미지 최적화 (CRITICAL)

Image fill에 sizes 추가

Next.js의 <Image fill /> 컴포넌트에 sizes prop이 없으면 기본값 100vw로 처리되어, 40px 썸네일에도 전체 뷰포트 크기 이미지를 요청하게 됩니다.

16개 Image 컴포넌트에 실제 렌더링 크기에 맞는 sizes를 추가했습니다:

tsxtsx
// 40px 원형 EGO 이미지
<Image fill sizes="40px" />

// 반응형 히어로 이미지
<Image fill sizes="(max-width: 768px) 100vw, 62vw" priority />

또한 Vercel Image Optimization을 비활성화(unoptimized: true)하여 월 $4.46의 변환 비용을 $0으로 줄였습니다. 이미지가 이미 Supabase Storage에서 WebP로 서빙되므로 이중 최적화가 불필요했습니다.

Chunk 3: 리렌더링 방지

React.memo 도입

거울 던전 페이지의 GiftCard 컴포넌트는 418개의 기프트 카드를 렌더링합니다. 필터 변경 시 모든 카드가 불필요하게 리렌더링되고 있었습니다.

tsxtsx
// Before: 매번 리렌더
function GiftCard({ gift, onClick }) { ... }

// After: props 변경 시에만 리렌더
const GiftCard = memo(function GiftCard({ gift, onClick }) { ... })

추가로:

  • useCallback으로 이벤트 핸들러 메모이제이션
  • useMemo 누락된 파생 데이터에 추가
  • IntersectionObserver 의존성 최적화 (과다 재생성 방지)

Chunk 4: SEO 강화

generateMetadata 추가

인격 상세, EGO 상세 등 핵심 콘텐츠 페이지에 generateMetadata가 없어 검색 엔진에 메타데이터가 노출되지 않았습니다.

tsxtsx
export async function generateMetadata({ params }) {
  const { slug, id } = await params
  // DB에서 인격 정보 조회
  return {
    title: `${identity.name_kr} | ${sinner.name_kr}`,
    description: `${identity.name_kr} - HP ${identity.hp}, 스킬/패시브 정보`,
    openGraph: {
      images: [{ url: identityImageUrl }],
    },
  }
}

사이트맵 확장

기존 16개 URL → 수백 개 URL로 확장. 인격/EGO 개별 페이지를 동적으로 생성하도록 변경했습니다.

메타 키워드 강화

"림버스 정보 사이트", "거던 조합식", "거던 레시피" 등 실제 검색어를 meta keywords에 추가했습니다.

Chunk 5: 접근성 (WCAG AA)

모달 접근성

SearchModal과 ConfirmModal에 role="dialog", aria-modal="true" 등 ARIA 속성을 추가하여 스크린리더가 모달을 올바르게 인식하도록 했습니다.

색상 대비 개선

서브텍스트 색상(#555566 등)이 AA 기준(4.5:1)에 미달하여 밝게 조정했습니다.

결과

항목BeforeAfter
클라이언트 번들서버 코드 포함개별 import
Image 요청 크기100vw 기본값실제 크기 기준
리렌더링전체 트리memo + useCallback
SEO 페이지 커버리지16 URL수백 URL
접근성모달 ARIA 없음WCAG AA 준수
Vercel 이미지 비용$4.46/월$0

기술 스택

  • Next.js 16 (App Router, Turbopack)
  • React 19 + TypeScript
  • Supabase (PostgreSQL)
  • Tailwind CSS 4
  • GSAP (애니메이션)

GitHub: INIRU/SAIGO-NO-DANTE 사이트: saigo-no-dante.com

이전 글

CodeQuest — GitHub 코드 기반 AI 코딩 퀴즈 플랫폼

다음 글

최애의 관리자 — 스토리 뷰어부터 플래너까지, 주요 기능 개발기

관련 글