0368

FE

[SvelteKit] SvelteKit 프로젝트에서 ESLint 문제를 해결한 여정

| 서론

안녕하세요 팡일입니다.

프론트엔드 프로젝트를 일정 수준 이상 키우다 보면 자연스럽게 ‘코드 품질’이라는 벽을 마주하게 됩니다. 기능은 돌아가는데, 코드 스타일은 들쑥날쑥하고, 타입은 점점 any의 바다로 밀려오고, 조금만 방심하면 오류가 사방에서 터지는 경험… 저만 한 건 아니겠죠?

그래서 저는 이번 SvelteKit + Firebase 기반 프로젝트인 Re-log에서 ESLint를 중심으로 코드를 리팩토링했습니다. 단순히 “코드를 예쁘게 정리했다”를 넘어서, 전후 비교가 가능한 자동 측정 스크립트까지 만들어 리팩토링 효과를 정량적으로 증명했어요.

이 글은 그 과정과 배운 점을 정리한 개발 기록입니다.

| ESLint란? 왜 중요한가?

ESLint는 JavaScript/TypeScript 코드에서 아래와 같은 것들을 자동으로 검사해주는 정적 분석 도구입니다.

  • ? 잠재적 버그

  • ? 일관성 없는 코드 스타일

  • ? 보안 취약점

  • ? 잘못된 패턴

프레임워크나 프로젝트 크기가 커질수록 다음과 같은 문제를 미리 발견하는 데 도움됩니다.

  • 사용되지 않는 변수 (no-unused-vars)

  • 타입 안전성 부족 (no-explicit-any)

  • Promise 처리 누락

  • DangerouslySetInnerHtml 같은 XSS 관련 경고

  • SvelteKit의 goto() 관련 안전 경고

특히 SvelteKit은 라우팅/스토어/서버 액션이 복잡하게 얽히기 때문에 ESLint를 걸면 꽤 많은 문제들이 한꺼번에 발견됩니다.

| 프로젝트에서 ESLint를 어떻게 사용했는가

초기에는 단순히 다음 명령어로 검사만 하고 있었습니다.

code
npx eslint .

하지만 시간이 지날수록 이런 문가 생기곤 했습니다.

  • 변경점 확인 어려움

  • 리팩토링 효과 측정 불가

  • 오류 뭉텅이 때문에 해결해야 할 목록 파악 힘듦

그래서 저는 리팩토링 전/후를 자동으로 비교할 수 있는 스크립트를 만들었습니다.

| ESLint 전/후 결과 비교 자동화 스크립트

아래는 실제로 사용했던 eslint-compare.sh입니다.

bash
#!/bin/bash

mkdir -p eslint-reports

echo "# ? ESLint 리포트 생성 (전 상태)"
npx eslint . -f json -o eslint-reports/eslint-before.json
echo "✔ 리포트 저장됨: eslint-reports/eslint-before.json"

echo -e "\n========================================"
echo "? 리팩토링 진행 후 Enter 눌러주세요"
echo "========================================"
read

echo "========================================"
echo "? ESLint 리포트 생성 (후 상태)"
echo "========================================"
npx eslint . -f json -o eslint-reports/eslint-after.json
echo "✔ 리포트 저장됨: eslint-reports/eslint-after.json"

before_count=$(jq 'map(select(.errorCount > 0)) | length' eslint-reports/eslint-before.json)
after_count=$(jq 'map(select(.errorCount > 0)) | length' eslint-reports/eslint-after.json)

echo -e "\n========================================"
echo "? ESLint 차이 비교"
echo "========================================"
echo "? 리팩토링 전 문제 수: $before_count"
echo "? 리팩토링 후 문제 수: $after_count"

if [ "$before_count" -gt "$after_count" ]; then
    diff=$((before_count - after_count))
    echo "✨ 개선됨: 총 $diff 개 해결!"
else
    echo "⚠️ 개선된 문제 없음"
fi

echo -e "\n========================================"
echo "? 완료!"
echo "========================================"

이 스크립트를 실행하면 아래와 같은 결론이 나옵니다.

TypeScript
========================================
? ESLint 차이 비교
========================================
? 리팩토링 전 문제 수: 60
? 리팩토링 후 문제 수: 0
✨ 개선됨:60 개 해결!

이는 단순히 “코드 고쳤다”가 아니라 “얼마나 개선했는지 수치로 증명”할 수 있게 되었어요.

| 리팩토링 과정에서 실제로 마주했던 ESLint 문제들

아래는 제가 실제로 겪은 오류들입니다.

1) no-explicit-any — any를 제거하는 대장정

(1) 예시 오류

code
error  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any

(2) 해결 방법

  • Firebase 응답 타입을 직접 인터페이스로 정의

  • 내부적으로 사용하는 Modal, Store 타입 명확히 분리

  • unknown → 타입 좁히기(narrowing) 적용

  • DTO 형태로 적절한 타입 가드 추가

(3) 예시 개선

code
export type EditUserResult = {
  displayName?: string;
  email?: string;
  photoURL?: string;
} | null;

2) goto() 사용 시 — svelte/no-navigation-without-resolve

SvelteKit은 goto() 호출 시 네비게이션 중복 문제를 막기 위해 “resolve 없어도 괜찮아?”라고 경고합니다.

(1) 해결 방법

  • 모달 흐름을 개선해 resolve를 정상적으로 실행하도록 구조 변경

  • 필요하면 라우트 전환을 하나로 통합

  • eslint-disable-next-line 반복 사용을 최대한 줄임

3) {@html ...} — XSS 위험성

code
error  `{@html}` can lead to XSS attack  svelte/no-at-html-tags

Svelte의 중요한 경고 중 하나인 Markdown 렌더링에 {@html}을 사용해야 했기 때문에 무시할 수 없었습니다.

(1) 해결

처음에는 DOMPurify + markdown-it 조합을 통해 완전한 sanitize 처리 후 사용하고자 했지만, 임시 방편으로 주석을 달아 해결했습니다.

TypeScript
<div class="section">
	<h3>{title}</h3>
	<div class="preview">
		<!-- eslint-disable-next-line svelte/no-at-html-tags -->
		{@html renderMarkdown(content || '')}
	</div>
</div>

4) 타입이 string | null | undefined → string만 허용

이 부분은 가장 많이 나온 TS 에러였습니다.

code
Argument of type 'string | null | undefined' is not assignable to parameter of type 'string'.

(1) 예시

code
customWelcome(user?.displayName);

(2) 해결

code
customWelcome(user?.displayName ?? '사용자');

이렇게 default 값을 강제할 때 타입 안정성과 사용자 경험까지 챙겨질 수 있었습니다.


5) CreatedAt | undefined 에러

: Firebase Timestamp 타입의 문제들도 있었습니다.

(1) 해결 방법:

- 타입을 확실히 nullable 처리

code
export type CreatedAt = Timestamp | null;

- 컴포넌트에서 fallback 적용

HTML
<DetailHeader createdAt={data.createdAt ?? null} />

- formatDate에서도 명확하게 처리

code
export function formatDate(ts?: Timestamp | null): string {
  if (!ts) return '';
  …
}

6) UserDoc vs User 타입 충돌

Firebase Auth의 User 타입과 Firestore에서 가져온 UserDoc은 완전히 다른 구조입니다.

그래서 이런 오류가 발생함:

code
Type 'UserDoc' is not assignable to type 'User'

(1) 해결

  • Auth용 User, Firestore userDoc을 별도 타입으로 관리

  • Modal에서는 “편집에 필요한 UserDoc 형태만” 받도록 타입 수정

  • updateUserProfile()에 User 타입 강제하지 않고 UserDoc 형태의 DTO 설계

| 결국 ESLint가 알려준 것

리팩토링 동안 ESLint가 계속 알려준 건 이런 메시지였습니다.

  • “이 코드 제대로 동작 안 할 수도 있어”

  • “타입이 불안정해”

  • “이 입력은 null일지도 몰라”

  • “여기 XSS 터질 수 있어”

  • “경로 이동 중 중복 발생 가능해”

  • “이 변수 필요 없어”

단순한 규칙 체크가 아니었고 기능은 되지만 위험한 코드들을 모두 끄집어내 준 도구였어요.

| 리팩토링 후 얻은 변화

  • ESLint 오류 60 → 0

  • 타입 안정성 확보

  • 보안 취약점 제거 (XSS 처리)

  • 스토어 구조 안정화

  • auth 흐름 일관성 확보

  • 모달/네비게이션 구조 개선

  • 코드 가독성 향상

  • 유지보수 비용 절감

그리고 무엇보다 "정량적으로 증명되는” 리팩토링 결과가 생겼다는 점입니다. 이건 팀 프로젝트, 포트폴리오, 면접 어디에서도 강력한 증거가 됩니다.

| 마무리: 정적 분석은 귀찮지만 가장 많이 도와주는 도구다

ESLint는 처음엔 귀찮고, 때로는 ‘왜 이렇게까지 엄격해?’ 싶기도 합니다. 하지만 시간이 지날수록 깨닫게 되는 것 같습니다

ESLint는 나를 귀찮게 하는 게 아니라 나를 대신해서 문제를 찾아주는 “코드의 두 번째 눈”처럼 말이죠.

그리고 리팩토링을 할 때 전후 비교 스크립트처럼 ‘수치화’하는 순간 개발자로서 한 단계 레벨업한 느낌이 듭니다.