개발 철학

Alpine.js 작업 과정 & React 프로젝트와의 비교

17
이온디
Alpine.js 작업 과정 & React 프로젝트와의 비교

Alpine.js 작업 과정 & React 프로젝트와의 비교

Alpine.js 작업 과정 & React 프로젝트와의 비교

실제 버그, 이슈, 해결 과정 정리

작성일: 2025년 12월 4일 주제: 6개월 Alpine.js 개발에서 만난 문제들과 React 프로젝트와의 실제 비교 대상: 기술 의사결정자, 개발팀


목차

  1. Alpine.js 작업 과정
  2. 만난 문제들과 해결
  3. React 프로젝트의 현황
  4. 실제 비교 분석
  5. 결론

️ Alpine.js 작업 과정

Phase 1: HTMX에서 Alpine.js로 마이그레이션 (2개월)

목표

  • HTMX 완전 제거
  • Alpine.js로 상태 관리 전환
  • 기존 페이지 호환성 유지

구현 내용

1단계: 기본 컴포넌트 작성

// /layouts/el_d1/src/js/components/board-list.js
export function boardList(config = {}) {
    return {
        // 상태
        items: [],
        loading: false,
        currentPage: 1,
        searchKeyword: '',

        // 메서드
        async init() { ... },
        async loadItems(append = false) { ... },
        async applyFilters() { ... }
    }
}

작업량: 약 300줄 시간: 약 8시간 문제: 초기 설계부터 많은 고민


Phase 2: 각 페이지에 적용 (3개월)

적용된 페이지

  • ✅ QNA (커뮤니티) - qna.blade.php
  • ✅ Expert (전문가) - expert.blade.php
  • ✅ Guide (가이드) - guide.blade.php
  • ✅ Homepage Solution (플랫폼) - homepage_solution.blade.php
  • ✅ MyPage (마이페이지) - mypage.blade.php

각 페이지별 이슈

QNA 페이지 (가장 복잡)

구현 내용:
- 게시글 목록 (무한 스크롤)
- 검색/필터/정렬
- 페이지네이션
- 사이드바 (인기글)

코드 규모: 약 500줄
시간: 약 15시간

만난 문제들과 해결

Issue #1: 검색 필터 동기화 문제 (가장 심각)

증상

URL이 변경되지만 리스트가 업데이트 안 됨
/qna?search_keyword=blade&search_target=title_content
→ 리스트: 변화 없음 ❌

원인 분석 과정

Step 1: 현상 파악

// qna.blade.php에서
@php
    $initial_documents = [...]  // PHP에서 미리 로드
@endphp

<div x-data="boardList({
    initialData: @json($initial_documents)
})">
    <template x-for="item in items">...</template>
</div>

Step 2: 문제 발견

// board-list.js의 init() 함수
async init() {
    if (config.initialData && config.initialData.length > 0) {
        // ❌ 여기서 return하므로 loadItems() 호출 안 됨!
        return;
    }
    await this.loadItems();
}

Step 3: 근본 원인 - PHP @foreach()로 렌더링된 데이터 존재 - Alpine.js는 이 데이터를 사용하려고 함 - 검색 URL 파라미터가 있어도 처리 안 됨 - <template x-for>와 PHP @foreach 충돌

Step 4: 해결책 개발 (3가지 제시)

방법 1: PHP 렌더링 제거 (권장)

async init() {
    const hasSearchParams = !!(urlSearchKeyword || urlCategory);

    if (config.initialData && !hasSearchParams) {
        // URL 파라미터 없을 때만 초기 데이터 사용
        return;
    }

    // 파라미터 있으면 항상 API 호출
    await this.loadItems();
}

방법 2: 조건부 렌더링

@if(!$search_keyword && !$selected_category)
    @foreach($initial_documents as $doc)
        <!-- PHP 렌더링 -->
    @endforeach
@else
    <!-- Alpine.js 렌더링 -->
    <template x-for="item in items">...</template>
@endif

방법 3: API 항상 호출

// 초기 데이터 무시, 항상 API에서 로드
async init() {
    this.searchKeyword = getUrlParam('search_keyword');
    await this.loadItems();
}

결과: 3가지 문서화, 개발자 선택 가능 해결 시간: 약 6시간 재발생: 이후 비슷한 구조의 페이지에서 반복


Issue #2: 콘솔 에러 (preload 속성 누락)

증상

❌ <link rel=preload> must have a valid `as` value

원인

<!-- layout.html line 86 -->
<load target="./assets/css/d1.bundle.css" />

<!-- XE 엔진이 생성하는 HTML -->
<link rel="preload" href="...css">
<!-- as 속성 없음! -->

해결

<!-- 직접 링크 태그 사용 -->
<link rel="stylesheet" href="/layouts/el_d1/assets/css/d1.bundle.css?v={{ $asset_version }}">

해결 시간: 약 1시간 영향도: 중간 (사용자 경험 영향 없음, 개발 환경 불편)


Issue #3: 403 Permission 에러 (브라우저 확장)

증상

❌ Uncaught (in promise) {code: 403, msg: 'permission error'}
경로: /writing/get_template_list, /site_integration/template_list

원인 분석

  • 브라우저 확장 프로그램이 요청 차단
  • Figma 플러그인, Wave 접근성 도구 등
  • 프로덕션에서는 발생하지 않음

해결

// 에러 처리로 무시
try {
    const response = await fetch(apiUrl);
    const data = await response.json();
} catch (err) {
    console.warn('API 호출 실패 (개발 환경):', err);
    // 무시
}

해결 시간: 약 0.5시간 영향도: 낮음 (개발 환경만)


Issue #4: 초기 데이터 vs API 데이터 불일치

증상

1. 페이지 로드 시: PHP에서 로드한 데이터 표시
2. 사용자가 필터 변경: API에서 로드한 다른 데이터
3. 데이터 구조 미묘한 차이로 인해 렌더링 오류

원인

// API 응답
{
    "documents": [
        "document_srl": 123,
        "title": "...",
        "content": "...",
        "nick_name": "..."
    ]
}

// Blade 객체
$doc->document_srl
$doc->getTitle()
$doc->getNickName()

// 구조 다름!

해결

// API 응답을 Blade 객체 형식으로 변환
foreach ($documents as $doc) {
    $document = new Document();
    $document->document_srl = $doc->document_srl;
    // ... 변환 로직
}

해결 시간: 약 2시간 재발생: 매번 새 페이지 추가할 때마다 발생


Issue #5: 상태 동기화 문제

증상

사용자 행동:
1. 검색어 입력
2. 엔터 누름
3. URL 변경
4. 뒤로가기

결과:
- 검색어 초기화됨
- 페이지 상태 불일치

원인

// localStorage에 저장했는데
localStorage.setItem('qna_search', 'blade');

// 브라우저 뒤로가기 시
// loadItems()가 호출되지 않음

해결

window.addEventListener('popstate', () => {
    // URL 파라미터 다시 파싱
    const params = new URLSearchParams(window.location.search);
    this.searchKeyword = params.get('search_keyword') || '';
    this.loadItems();
});

해결 시간: 약 3시간 현재 상태: 부분 해결 (완벽하지 않음)


Issue #6: 렌더링 성능 문제

증상

리스트에 500개 이상 아이템 추가 시
- UI 반응 느려짐
- 스크롤 버벅거림

원인

// <template x-for>는 모든 아이템을 DOM에 추가
// 가상 스크롤링 없음
items.length = 500 // → 500개 DOM 노드 생성

해결

// 무한 스크롤 적용
async loadMore() {
    if (!this.hasMore) return;
    this.currentPage++;
    await this.loadItems(true);  // append=true
}

// 또는 라이브러리 도입
// tanstack/react-virtual 같은 것 (React에서는 가능)

해결 시간: 약 4시간 현재 상태: 무한 스크롤로 부분 해결


Issue #7: 타입 안전성 부족

증상

// 이런 실수가 발생
item.tilte  // ← 오타 (title)
item.nick_Name  // ← 케이스 실수
item.content.substring()  // ← content가 null일 수 있음

원인

  • JavaScript는 동적 타입
  • IDE 자동완성 불충분
  • 런타임 에러 발생

해결 (하지만 Alpine.js는 TypeScript 미지원)

// React에서는 이렇게 가능
interface Document {
    document_srl: number;
    title: string;
    content: string;
    nick_name: string;
}

const item: Document = {
    document_srl: 123,
    title: '테스트',
    // ... 타입 체크!
}

해결 시간: N/A (Alpine.js에서 불가능) 영향도: 중간 (런타임 에러 증가)


Issue #8: 상태 관리 복잡도

증상

// 여러 상태가 얽혀 있음
x-data="Object.assign(
    boardList({...}),
    {
        localSearchVisible: false,
        filterVisible: false,
        sortMenuOpen: false,
        categoryHovered: null,
        ...
    }
)"

문제점

  • 상태 간 의존성 불명확
  • 업데이트 로직 분산
  • 리팩토링 어려움

React에서는

// 명확한 상태 관리
const [searchKeyword, setSearchKeyword] = useState('');
const [currentPage, setCurrentPage] = useState(1);
const [items, setItems] = useState([]);

// 각 상태의 역할이 명확

영향도: 높음 (프로젝트 규모 증가할수록 악화)


Alpine.js 작업 통계

개발 시간 분석

Phase 1: HTMX → Alpine.js 마이그레이션
- 기본 컴포넌트: 8시간
- 문서 작성: 4시간
└─ 소계: 12시간

Phase 2: 페이지별 적용
- QNA: 15시간
- Expert: 12시간
- Guide: 8시간
- Homepage Solution: 10시간
- MyPage: 6시간
└─ 소계: 51시간

Phase 3: 버그 수정
- Issue #1 (검색 필터): 6시간
- Issue #2 (preload): 1시간
- Issue #3 (403 에러): 0.5시간
- Issue #4 (데이터 불일치): 2시간
- Issue #5 (상태 동기화): 3시간
- Issue #6 (성능): 4시간
- Issue #7 (타입): 0시간 (미해결)
- Issue #8 (복잡도): 0시간 (지속 중)
└─ 소계: 16.5시간

전체: 79.5시간 ≈ 80시간 (약 10일)

문제 발생 빈도

개발 중 버그: 8개
재발생: 4개 (Issue #1, #4, #5, #8)
해결율: 50% (완전 해결)

React 프로젝트의 현황

프로젝트 구조

/layouts/el_imin_react/
├── src/
│   ├── components/
│   │   ├── Header.tsx
│   │   ├── Navigation.tsx
│   │   ├── Footer.tsx
│   │   └── ...
│   ├── pages/
│   │   ├── Board/
│   │   └── ...
│   ├── contexts/
│   │   └── RhymixContext.tsx
│   ├── App.tsx
│   └── index.tsx
├── assets/
├── layout.html
└── conf/

/modules/board/skins/eb_imin_react/
├── src/
│   ├── components/
│   └── ...
└── ...

구현 상태

Header Component: ✅ 기본 완성
Navigation: ✅ 기본 완성
Footer: ✅ 기본 완성
RhymixContext: ✅ 기본 구조
Board Pages: ⏳ 진행 중

React 도입 배경

Timeline:
2024년 초: el_imin_react 프로젝트 시작
2024년 중: TypeScript + React 기본 구조 구축
2024년 말: 컴포넌트 분리 및 상태 관리 개선
2025년 현재: Alpine.js와 React 병행

실제 비교 분석

개발 속도 비교

Alpine.js로 검색 필터 페이지 만들기

작업:
1. HTML 마크업: 1시간
2. Alpine.js 상태: 1시간
3. API 연동: 1시간
4. 버그 수정: 2-3시간 ← 예상 밖
5. 스타일: 1시간

총: 6-7시간
버그로 인한 지연: 흔함

React로 같은 페이지 만들기

작업:
1. 컴포넌트 설계: 1시간
2. 상태 관리 (Zustand): 1시간
3. API 연동 (axios): 0.5시간
4. 테스트 작성: 0.5시간
5. 스타일: 1시간

총: 4시간
버그 가능성: 낮음

결론: React가 처음엔 복잡하지만, 규모 커질수록 더 빠름


유지보수 비용

Alpine.js

3개월 후 현황:
- 버그 보고: 월 2-3개
- 수정 시간: 각각 2-3시간
- 코드 이해도: 작성자만 알겠음

예상 월 비용: 10-15시간

React

3개월 후 현황:
- 버그 보고: 월 0-1개
- 수정 시간: 0.5-1시간
- 코드 이해도: 팀 전체가 이해

예상 월 비용: 2-3시간

확장성 비교

새 기능 추가: 실시간 협업 댓글

Alpine.js

// WebSocket 연동 필요
// 상태 관리 복잡
// 타입 안전성 없음
// 예상 개발 시간: 20시간
// 버그 가능성: 높음

// 현재: 미구현

React

// 상태 관리 명확
// 타입 안전성 있음
// 테스트 가능
// 예상 개발 시간: 15시간
// 버그 가능성: 낮음

// 현재: 기본 구조 준비 중

성능 비교

번들 크기

Alpine.js:
- Alpine.js 라이브러리: 15KB
- 컴포넌트 코드: ~30KB (모든 페이지)
└─ 총: ~45KB ✅

React (el_imin_react):
- React + ReactDOM: 43KB
- 컴포넌트 코드: ~50KB
- State Management (Zustand): 3KB
└─ 총: ~96KB ⚠️

차이: React가 2배 크지만, 현대 브라우저에선 무시할 수준

초기 로딩 속도

Alpine.js:
- HTML 파싱: 100ms
- Alpine 초기화: 50ms
- 데이터 렌더링: 150ms
└─ 총: 300ms ✅

React (SSR 없을 때):
- HTML 파싱: 100ms
- React 번들 로드: 200ms
- 컴포넌트 렌더링: 300ms
└─ 총: 600ms ⚠️

React (SSR 있을 때):
- PHP SSR: 200ms
- HTML 전송: 100ms
- React 하이드레이션: 100ms
└─ 총: 400ms (향상)

결론: React + SSR이 더 나음


개발자 경험 비교

디버깅

Alpine.js

// 문제 발생
// Vue DevTools로는 안 봄
// console.log로만 추적
// 상태 변화 추적 어려움

React

// React DevTools로 모든 상태 볼 수 있음
// 컴포넌트 업데이트 추적 가능
// 상태 변화 명확히 보임
// 타입 에러는 IDE에서 즉시 표시

React 압승리

코드 리팩토링

Alpine.js

// x-data="boardList({...})" 이 파일에 의존
// 컴포넌트 분리 어려움
// 로직 추출 제한적
// 재사용성 낮음

React

// 명확한 컴포넌트 경계
// 로직 추출 간단
// 커스텀 훅으로 재사용
// 테스트 용이

React 압승리

팀 온보딩

Alpine.js - 신입: "이게 뭐예요?" (첫 주 어려움) - 2주 후: 기본 이해 - 1개월 후: 충분히 작업 가능

React - 신입: "React는 알아요" (시장성) - 2주 후: 프로젝트 패턴 이해 - 1개월 후: 충분히 작업 가능

비슷함


핵심 통찰

Alpine.js가 잘하는 것

✅ 간단한 상호작용 (토글, 드롭다운)
✅ SEO 중요한 페이지 (PHP SSR)
✅ 작은 팀 프로젝트
✅ 빠른 프로토타입
✅ 낮은 학습 곡선 (경험자에게)

React가 잘하는 것

✅ 복잡한 상태 관리
✅ 팀 협업
✅ 대규모 애플리케이션
✅ 장기 유지보수
✅ 실시간 기능
✅ 채용 시장성

우리의 실패 요인

Alpine.js로는 못 해낸 것들

1. 타입 안전성 확보 ❌
   → JavaScript 동적 언어 한계

2. 복잡한 상태 관리 ❌
   → 8개 Issue 중 4개가 상태 관리

3. 팀 협업 효율성 ❌
   → 각자 다르게 구현

4. 재사용 가능한 컴포넌트 ❌
   → 각 페이지마다 새로 작성

5. 확정적인 데이터 흐름 ❌
   → 어디서 업데이트되는지 불명확

결론: 우리는 왜 React를 시작했나?

Alpine.js 80시간의 교훈

1주일 개발: 매우 빠름
2주일 개발: 버그 시작
1개월 개발: 유지보수 비용 증가
3개월 개발: "이걸 React로 다시 만들걸..."

React 프로젝트가 진행 중인 이유

현실:
- Alpine.js는 충분하지 않았음
- 복잡한 기능 추가 어려움
- 팀 확장 제한적
- 기술 부채 누적

해결책:
- React 병행 시작
- 새 기능은 React로
- 기존 Alpine은 유지
- 점진적 마이그레이션

최종 권장사항

우리의 상황 (2025년 12월 기준)

✅ 완료: Alpine.js 기본 기능
✅ 완료: PHP SSR + Alpine 하이브리드
✅ 진행 중: React 프로젝트 구축
⏳ 계획: 점진적 마이그레이션

실제 진척:
- Alpine.js: 80시간 투자, 50% 만족도
- React: 초기 단계, 기대도 높음

내가 해야 할 것

Step 1: React 프로젝트 완성 (2-3개월)
- 기본 레이아웃 완성
- 핵심 컴포넌트 구현
- 상태 관리 최적화

Step 2: Alpine.js와의 공존 관리 (3개월)
- 두 기술의 충돌 최소화
- 명확한 사용 기준 수립
- 팀 교육

Step 3: 선택적 마이그레이션 (6개월 이후)
- 복잡한 기능은 React로 변환
- 간단한 기능은 Alpine.js 유지
- 이중 성과 달성

팀에게

현재 상황:
- Alpine.js는 나쁜 선택이 아니었다
- 하지만 다음 단계로 가야 한다
- React는 이미 준비 중이다

좋은 소식:
- 우리는 두 기술 다 경험했다
- 각각의 장단점을 알고 있다
- 최적의 선택을 할 수 있다
- 기술 부채가 적다 (초기 단계에 발견)

최종 통계

Alpine.js 투자

개발 시간: 80시간
버그 이슈: 8개
해결율: 50%
재발생률: 50%
개발자 만족도: 6/10
기술 부채: 중간

React 프로젝트

개발 시간: 초기 단계
버그 이슈: 0개 (아직)
예상 개발율: 70%
개발자 만족도: 예상 8/10
기술 부채: 낮음

앞으로의 방향

선택지

Option 1: Alpine.js로 끝까지 간다
- 단기 빠름
- 장기 비용 높음
- 리스크 높음

Option 2: React로 완전 전환
- 초기 비용 높음
- 장기 효율적
- 확장성 우수

Option 3: 하이브리드 운영 (추천)
- 균형 잡힘
- 점진적 전환
- 리스크 최소화

우리의 선택: Option 3 (이미 진행 중)


마지막 말

"완벽한 기술은 없다. 문제를 푸는 과정에서 배우고, 그 경험으로 다음 선택을 한다. 우리는 지금 그 과정의 중간쯤에 있다."

80시간의 Alpine.js 개발은 낭비가 아니다. 그것이 React 프로젝트의 기반이 되었기 때문이다.


작성: 2025-12-04 참고자료: Alpine.js 8개월 경험, React 초기 단계 검토: 개발팀 전체

프로젝트를 함께 만들고 싶다면

지금 바로 문의해 보세요

댓글 0

첫 번째 댓글을 작성해 보세요.