Solve Desktop 개발 회고

회사에서 교육용 iOS 앱을 데스크톱으로 포팅하는 프로젝트를 맡게 됐다. 처음엔 단순한 포팅 작업인 줄 알았는데, 막상 시작해보니 완전히 새로운 앱을 만드는 수준이었다.

사용자들이 더 큰 화면에서 학습하고 싶다는 요청이 많이 들어와서, Windows와 macOS에서 모두 돌아가는 데스크톱 앱을 만들기로 했다. 처음엔 Electron을 생각했는데, 메모리 사용량이 걱정되더라. 그러다 Tauri라는 걸 알게 됐는데, Rust 기반이면서도 웹 기술을 쓸 수 있다는 게 매력적이었다.

다행히 기존 iOS 앱이 잘 만들어져 있어서 많은 부분을 참고할 수 있었다. API 구조, 인증 플로우, 보안 정책 같은 핵심 로직들은 그대로 가져올 수 있었고, UI/UX도 iOS 앱을 기준으로 데스크톱에 맞게 조정하는 방식으로 진행했다. 덕분에 기획 단계에서 헤맬 시간을 많이 절약할 수 있었다.

기술 스택 고민하기

프론트엔드는 평소에 쓰던 React + TypeScript로 가기로 했다. 회사에서 이미 쓰고 있던 스택이라 팀원들도 익숙하고, TypeScript 덕분에 런타임 에러도 많이 줄일 수 있어서 좋았다.

CSS는 Tailwind를 선택했는데, 디자인 시스템을 빠르게 구축할 수 있어서 시간을 많이 절약했다. 상태 관리는 Redux 대신 Zustand를 써봤는데, 정말 간단하면서도 필요한 건 다 있더라.

iOS 앱을 데스크톱으로 옮기기

기존 iOS 앱의 구조를 최대한 참고했다. 특히 API 엔드포인트나 데이터 구조는 거의 그대로 가져올 수 있었다.

// iOS AuthService와 동일한 구조로 구현
export class AuthService {
  static async getUser(): Promise<UserResponse> {
    // iOS와 동일한 /user/me 엔드포인트 사용
    return await GET<UserResponse>("user/me", {});
  }

  static async ssoLogin(ssoPlatformId: string, ssoPlatformTypeName: string) {
    // iOS와 동일한 SSO 로그인 API 사용
    return await PUBLIC_POST("auth/login/sso", {
      sso_platform_id: ssoPlatformId,
      sso_platform_type_name: ssoPlatformTypeName,
    });
  }
}

Analytics 시스템도 iOS에서 쓰던 Mixpanel을 그대로 가져왔다. 사용자 식별 로직이나 이벤트 추적 방식을 동일하게 맞춰서 데이터 일관성을 유지할 수 있었다.

// Zustand 스토어가 이렇게 간단하다
const useAuthStore = create((set) => ({
  user: null,
  isAuthenticated: false,
  login: async (mobile_number, code, issueId) => {
    // 로그인 로직
  },
}));

Tauri v2를 선택한 건 정말 잘한 것 같다. 처음엔 레퍼런스가 적어서 걱정했는데, 막상 써보니 Electron보다 훨씬 가벼웠다. 번들 크기도 80MB 정도로 적당하고, 메모리 사용량도 150MB 미만으로 유지되더라.

개발하면서 겪은 일들

로그인 시스템 완전 개편

처음에 가장 골치 아팠던 게 로그인 시스템이었다. iOS 앱에서는 카카오, 애플, 구글 SDK를 직접 연동해서 쓰고 있었는데, 이걸 데스크톱으로 그대로 옮기려니 너무 복잡하더라.

각 플랫폼마다 SDK 설정도 다르고, 인증 플로우도 제각각이라서 유지보수가 힘들었다. 그래서 아예 다른 방법을 생각해봤다.

딥링크를 활용하는 방법이었다. 사용자가 로그인 버튼을 누르면 브라우저로 쏠브북스 로그인 페이지를 열고, 로그인이 완료되면 딥링크로 다시 앱으로 돌아오게 하는 거다.

// 쏠브북스 로그인 서비스
export class SolveBooksLoginService {
  static async loginWithKakao(): Promise<void> {
    return this.openLoginPage({ platform: "kakao" });
  }

  static handleLoginCallback(url: string) {
    // solve-desktop://auth/callback 형태의 딥링크 처리
    const urlObj = new URL(url);
    const ssoPlatformId = urlObj.searchParams.get("sso_platform_id");
    const ssoPlatformTypeName = urlObj.searchParams.get(
      "sso_platform_type_name"
    );

    return {
      success: !!ssoPlatformId,
      ssoPlatformId,
      ssoPlatformTypeName,
    };
  }
}

이렇게 하니까 SDK 의존성도 없애고, 웹에서 이미 검증된 로그인 시스템을 그대로 쓸 수 있어서 훨씬 안정적이었다. 게다가 로그인 관련 로직은 백엔드에서 관리하니까 유지보수도 편해졌다.

보안은 정말 복잡하더라

교육 콘텐츠라서 보안이 정말 중요했다. 사용자들이 화면 캡처를 하거나 계정을 공유하는 걸 막아야 했거든.

워터마크부터 시작해서 동시접속 체크, 화면 녹화 감지, 기기 인증까지 여러 가지를 구현했다. 특히 동시접속 체크가 까다로웠는데, 30초마다 서버에 체크해서 다른 곳에서 로그인하면 자동으로 로그아웃시키는 방식으로 했다.

// 동시접속 체크 컴포넌트
const ConcurrentAccessChecker: React.FC = () => {
  useEffect(() => {
    const checkAccess = async () => {
      const result = await ConcurrentAccessService.checkAccess();
      if (result.hasConflict) {
        // 다른 곳에서 로그인했으면 강제 로그아웃
        handleSecurityViolation();
      }
    };

    const interval = setInterval(checkAccess, 30000);
    return () => clearInterval(interval);
  }, []);
};

화면 녹화 감지는 Tauri의 네이티브 기능을 활용했다. macOS나 Windows에서 화면 녹화가 시작되면 감지해서 경고를 띄우도록 했다.

// Tauri 백엔드에서 화면 녹화 감지
#[tauri::command]
pub fn detect_screen_recording_suspected() -> bool {
    // 플랫폼별 화면 녹화 감지 로직
    platform::detect_recording()
}

Analytics 시스템 통합

iOS 앱에서 쓰고 있던 Mixpanel과 Sentry를 데스크톱에도 연동했다. 사용자 행동을 분석하고 에러를 추적하기 위해서였는데, iOS와 최대한 동일한 구조로 만들려고 했다.

// Analytics 서비스 (iOS와 동일한 패턴)
export class AnalyticsService {
  static async init(mixpanelToken?: string): Promise<void> {
    // 환경별 토큰 관리
    const environment = import.meta.env.MODE;
    const appVersion = await versionService.getCurrentVersion();

    mixpanel.init(token, {
      debug: environment === "development",
      loaded: () => {
        mixpanel.register({
          Platform: "desktop",
          service_type: "solve-desktop",
          "App Version": appVersion,
        });
      },
    });
  }

  static identify(profile: UserProfile): void {
    mixpanel.identify(profile.userId);
    SentryService.identify(profile.userId, {
      email: profile.email,
      username: profile.name,
    });
  }
}

이제 iOS와 데스크톱에서 동일한 형태로 사용자 데이터를 수집하고 분석할 수 있게 됐다.

자동 업데이트 시스템

Tauri의 업데이트 시스템을 활용해서 자동 업데이트를 구현했다. GitHub Releases와 연동해서 새 버전이 나오면 사용자에게 알림을 주고 자동으로 업데이트할 수 있게 했다.

// 자동 업데이트 훅
const useForceUpdate = (intervalMinutes: number) => {
  const [updateInfo, setUpdateInfo] = useState(null);
  const [showForceUpdate, setShowForceUpdate] = useState(false);

  useEffect(() => {
    const checkForUpdates = async () => {
      const update = await check();
      if (update?.available) {
        setUpdateInfo(update);
        setShowForceUpdate(true);
      }
    };

    // 주기적으로 업데이트 체크
    const interval = setInterval(checkForUpdates, intervalMinutes * 60 * 1000);
    checkForUpdates(); // 즉시 실행

    return () => clearInterval(interval);
  }, [intervalMinutes]);

  return { updateInfo, showForceUpdate, handleUpdate, handleQuit };
};

버전 관리의 지옥에서 벗어나기

이거 진짜 미치는 줄 알았다. Tauri 앱은 버전 정보를 세 군데나 관리해야 한다:

// package.json
{
  "name": "solve",
  "version": "1.2.1" // 👈 여기
}
# src-tauri/Cargo.toml
[package]
name = "solve"
version = "1.2.1"     # 👈 여기
// src-tauri/tauri.conf.json
{
  "productName": "Solve",
  "version": "1.2.1" // 👈 여기도
}

문제가 뭐였냐면:

  1. 수동으로 세 파일을 다 업데이트해야 함 - 하나라도 빠뜨리면 빌드 실패
  2. 시맨틱 버저닝 계산이 복잡함 - 1.2.0-beta.4에서 다음 베타는 뭐지? 정식은?
  3. Git 태그와 버전 불일치 - 태그 만들기 까먹거나 잘못된 버전으로 태그 생성
  4. GitHub Actions 트리거 실패 - 태그 형식이 맞지 않으면 자동 빌드 안됨
  5. 팀원들마다 다른 릴리스 방식 - 각자 스타일대로 해서 혼란 가중

첫 번째 시도: 복잡한 커스텀 스크립트

처음엔 모든 걸 직접 구현했다. 정규식으로 버전 파싱하고, 시맨틱 버저닝 규칙에 따라 증가시키고…

// 이런 식으로 복잡하게 했었다...
function incrementVersion(currentVersion, type) {
  const versionRegex = /^(\d+)\.(\d+)\.(\d+)(?:-(\w+)\.(\d+))?$/;
  const match = currentVersion.match(versionRegex);
  // 100줄 넘는 복잡한 로직...
}

결과: 버그 투성이, 엣지 케이스 처리 안됨, 유지보수 지옥

두 번째 시도: npm version 발견!

그러다 npm에 이미 완벽한 버전 관리 명령어가 있다는 걸 알았다.

npm version patch       # 1.2.0 → 1.2.1
npm version minor       # 1.2.0 → 1.3.0
npm version major       # 1.2.0 → 2.0.0
npm version prerelease  # 1.2.0 → 1.2.1-0
npm version prerelease --preid=beta  # 1.2.0-beta.4 → 1.2.0-beta.5

npm이 알아서 package.json 업데이트하고, Git 태그도 만들어준다!

최종 해결책: npm + Tauri 동기화

npm version으로 package.json만 업데이트하고, 나머지 Tauri 파일들은 자동 동기화하는 스크립트를 만들었다.

// scripts/sync-tauri-version.cjs (핵심 로직만)
const fs = require("fs");

function syncTauriVersion() {
  // 1. package.json에서 버전 읽기
  const packageJson = JSON.parse(fs.readFileSync("package.json", "utf8"));
  const newVersion = packageJson.version;

  // 2. Cargo.toml 업데이트
  let cargoContent = fs.readFileSync("src-tauri/Cargo.toml", "utf8");
  cargoContent = cargoContent.replace(
    /^version\s*=\s*".*"$/m,
    `version = "${newVersion}"`
  );
  fs.writeFileSync("src-tauri/Cargo.toml", cargoContent);

  // 3. tauri.conf.json 업데이트
  const tauriConf = JSON.parse(
    fs.readFileSync("src-tauri/tauri.conf.json", "utf8")
  );
  tauriConf.version = newVersion;
  fs.writeFileSync(
    "src-tauri/tauri.conf.json",
    JSON.stringify(tauriConf, null, 2)
  );

  console.log(`✅ 모든 파일이 ${newVersion}으로 동기화됨`);
}

인터랙티브 릴리스 시스템

그리고 사용자 친화적인 메뉴까지 만들었다.

// scripts/interactive-release.cjs
function analyzeCurrentVersion(currentVersion) {
  const isPrereleaseVersion = currentVersion.includes("-");

  if (isPrereleaseVersion) {
    return [
      {
        command: "release-beta",
        description: `베타 업데이트 (${currentVersion}${getNextBeta(
          currentVersion
        )})`,
      },
      {
        command: "release-patch",
        description: `정식 승격 (${currentVersion}${getStableVersion(
          currentVersion
        )})`,
      },
    ];
  } else {
    return [
      {
        command: "release-patch",
        description: `패치 (${currentVersion}${getNextPatch(
          currentVersion
        )})`,
      },
      {
        command: "release-minor",
        description: `마이너 (${currentVersion}${getNextMinor(
          currentVersion
        )})`,
      },
      {
        command: "release-major",
        description: `메이저 (${currentVersion}${getNextMajor(
          currentVersion
        )})`,
      },
    ];
  }
}

패키지 스크립트 통합

package.json에 모든 릴리스 명령어를 정리했다.

{
  "scripts": {
    "version-check": "node scripts/sync-tauri-version.cjs",
    "release": "node scripts/interactive-release.cjs",

    // 직접 명령어들
    "release-beta": "npm version prerelease --preid=beta --no-git-tag-version && node scripts/sync-tauri-version.cjs && git add -A && git commit -m \"🧪 chore: beta version bump\" && git tag v$(node -p \"require('./package.json').version\") && git push && git push --tags",

    "release-patch": "npm version patch --no-git-tag-version && node scripts/sync-tauri-version.cjs && git add -A && git commit -m \"🔖 chore: patch version bump\" && git tag v$(node -p \"require('./package.json').version\") && git push && git push --tags"
  }
}

최종 사용 경험

$ npm run release

🚀 npm version 기반 인터랙티브 릴리스
📦 현재 버전: 1.2.0-beta.4

📊 프로젝트 상태 분석...
✅ Git 작업 디렉토리가 깨끗합니다.
🌿 현재 브랜치: release-1.2.0

🚀 릴리스 옵션:
1. release-beta        - 베타 업데이트 (1.2.0-beta.4 → 1.2.0-beta.5)
2. release-patch       - 정식 승격 (1.2.0-beta.4 → 1.2.0)
3. ❌ 취소

선택하세요 (1-3): 2

🚀 실행 중: npm run release-patch
✅ 모든 테스트 통과
✅ 프로덕션 빌드 성공
✅ Git 태그 v1.2.0 생성 및 푸시 완료
🎉 릴리스가 성공적으로 완료되었습니다!

개선 효과

Before (수동 관리):

  • ⏰ 릴리스 시간: 20-30분 (실수 수정 포함)
  • 😰 에러율: 30% (파일 놓치거나 버전 불일치)
  • 📝 500+줄의 복잡한 커스텀 로직

After (자동화):

  • ⚡ 릴리스 시간: 3-5분
  • ✅ 에러율: 거의 0% (npm 표준 + 간단한 동기화)
  • 🎯 200줄의 깔끔한 스크립트

이제 릴리스가 무서운게 아니라 재미있는 일이 됐다! 팀원 누구나 실수 없이 릴리스할 수 있게 됐고, CI/CD도 안정적으로 돌아간다.

GitHub Actions로 완전 자동화

여기서 끝이 아니다. Git 태그를 푸시하면 GitHub Actions가 자동으로 빌드하고 릴리스까지 해준다.

# .github/workflows/release.yml
name: Release App

on:
  push:
    tags:
      - "v*.*.*" # 정식: v1.2.3
      - "v*.*.*-beta.*" # 베타: v1.2.3-beta.1

jobs:
  release:
    strategy:
      matrix:
        include:
          - platform: "macos-latest"
            args: "--target aarch64-apple-darwin" # M1/M2/M3
          - platform: "macos-latest"
            args: "--target x86_64-apple-darwin" # Intel Mac
          - platform: "windows-latest"
            args: "--target x86_64-pc-windows-msvc" # Windows

핵심은 크로스플랫폼 빌드다. macOS에서 Apple Silicon과 Intel 두 버전을 모두 빌드하고, Windows용까지 총 3개 파일을 자동으로 만들어준다.

베타/정식 버전 자동 구분

워크플로우가 똑똑해서 태그 이름만 보고도 베타인지 정식인지 구분한다.

# 베타/정식 구분 로직
releaseBody: |
  $

prerelease: $
  • v1.2.0-beta.5 태그 → 베타 릴리스 (prerelease 체크)
  • v1.2.0 태그 → 정식 릴리스

Draft 릴리스로 안전 장치

처음엔 무조건 Draft 상태로 릴리스를 만든다. 모든 플랫폼 빌드가 완료된 후에 수동으로 Publish한다.

releaseDraft: true # ✅ 핵심: 빌드 중엔 항상 Draft
includeUpdaterJson: true # 자동 업데이트용 latest.json 생성

왜냐면 macOS 2개 + Windows 1개 = 총 3개 빌드가 완료되어야 하는데, 중간에 하나라도 실패하면 불완전한 릴리스가 될 수 있거든.

별도 저장소에 릴리스 관리 (Private → Public)

여기서 핵심은 코드 저장소는 Private, 릴리스 저장소는 Public으로 분리한 거다.

# cross-repo 릴리스 설정
env:
  GITHUB_TOKEN: $ # 👈 핵심: PAT 토큰 사용
with:
  owner: $
  repo: solve-desktop-releases # Public 릴리스 전용 저장소
  releaseCommitish: "main"

왜 이렇게 했냐면:

  1. 코드 보안: 메인 개발 저장소는 Private으로 소스코드 보호
  2. 사용자 접근성: 릴리스 저장소는 Public으로 누구나 다운로드 가능
  3. 깔끔한 분리: 사용자는 복잡한 소스코드 말고 릴리스만 보면 됨

PAT 토큰으로 Cross-Repository 권한 관리

GitHub Actions에서 다른 저장소에 릴리스를 업로드하려면 Personal Access Token이 필요하다.

# 일반 GITHUB_TOKEN으로는 다른 저장소 접근 불가
# GITHUB_TOKEN: $  # ❌ 안됨

# PAT 토큰 사용
GITHUB_TOKEN: $ # ✅ Cross-repo 가능

PAT 토큰 설정:

  1. GitHub Settings → Developer settings → Personal access tokens
  2. repo 권한 체크 (릴리스 저장소 접근용)
  3. 토큰을 메인 저장소의 Secrets에 PAT_TOKEN으로 저장

실제 릴리스 흐름

# Private 저장소 (testbank-inc/solve-desktop)
📁 solve-desktop (Private)
├── 소스코드 (비공개)
├── .github/workflows/release.yml
└── secrets.PAT_TOKEN

# 태그 푸시시 →

# Public 저장소 (testbank-inc/solve-desktop-releases)
📁 solve-desktop-releases (Public)
├── Release v1.2.0
│   ├── solve_desktop_1.2.0_aarch64.dmg    # Mac M1/M2/M3
│   ├── solve_desktop_1.2.0_x64.dmg        # Mac Intel
│   ├── solve_desktop_1.2.0_x64_setup.exe  # Windows
│   └── latest.json                         # 자동업데이트
└── 사용자들이 여기서 다운로드

장점:

  • 소스코드는 안전하게 보호
  • 사용자는 간편하게 다운로드
  • 바이너리로 메인 저장소가 무거워지지 않음
  • 릴리스 저장소만 따로 관리 가능

자동 업데이트까지 완벽 지원

사용자가 앱을 실행하면 자동으로 새 버전을 체크한다.

includeUpdaterJson: true # latest.json 자동 생성
updaterJsonPreferNsis: true # Windows NSIS 방식 선호

Tauri의 업데이트 시스템과 연동해서 latest.json 파일을 자동으로 생성하고, 앱에서 이걸 주기적으로 체크해서 업데이트 알림을 띄운다.

환경 변수 자동 주입

빌드할 때 필요한 환경 변수들도 자동으로 설정한다.

- name: Setup environment variables
  run: |
    echo "VITE_APP_VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV

특히 VITE_APP_VERSION은 Git 태그에서 v 접두사를 제거해서 자동으로 설정한다. v1.2.01.2.0

완전한 워크플로우

결국 전체 릴리스 과정이 이렇게 된다:

# 1. 개발자가 로컬에서 릴리스
$ npm run release
선택: 정식 릴리스 (1.2.0)

# 2. 자동으로 Git 태그 v1.2.0 생성 및 푸시

# 3. GitHub Actions 자동 트리거
✅ macOS Apple Silicon 빌드 완료
✅ macOS Intel 빌드 완료
✅ Windows x64 빌드 완료
✅ Draft 릴리스에 모든 파일 업로드 완료

# 4. 수동으로 Draft → Publish (검토 후)

# 5. 사용자들 자동 업데이트 알림 받음

총 소요 시간: 개발자 작업 1분 + GitHub Actions 빌드 10-15분 = 완전 자동화

이제 정말로 버전 하나 올리는 게 클릭 몇 번으로 끝난다. 예전엔 수동으로 각 OS별로 빌드하고 업로드하느라 몇 시간씩 걸렸는데 말이지.

AI 도구 덕분에 2주 만에 완성

솔직히 이 프로젝트를 2주 만에 완성할 수 있었던 건 AI 도구들 덕분이 큰 것 같다. 특히 Claude와 ChatGPT를 엄청 활용했다.

코드 작성 속도 향상

처음 보는 Tauri나 Zustand 같은 기술들을 배울 때 AI가 정말 도움이 됐다. “Tauri에서 딥링크 처리하는 방법” 같은 걸 물어보면 바로 예시 코드를 보여줘서 빠르게 구현할 수 있었다.

// AI가 제안해준 딥링크 처리 패턴
const handleDeepLink = (url: string) => {
  const urlObj = new URL(url);
  const params = Object.fromEntries(urlObj.searchParams);

  if (params.sso_platform_id) {
    // SSO 로그인 처리
    processSSOLogin(params);
  }
};

특히 복잡한 TypeScript 타입 정의나 Rust 코드 작성할 때도 AI한테 물어보면서 하니까 훨씬 빨랐다. 혼자 구글링하고 문서 찾아보는 시간을 엄청 절약했다.

디버깅과 문제 해결

에러 메시지를 AI한테 보여주면 원인과 해결책을 빠르게 찾아줘서 디버깅 시간도 많이 줄었다. 특히 크로스플랫폼 이슈나 Tauri 관련 에러들은 검색해도 자료가 별로 없었는데, AI가 비슷한 사례를 바탕으로 해결 방향을 제시해줘서 도움이 됐다.

문서화와 테스트 코드

README 작성이나 API 문서 정리할 때도 AI가 초안을 만들어주면 그걸 기반으로 다듬는 식으로 했다. 테스트 코드도 마찬가지로 AI가 기본 구조를 잡아주면 비즈니스 로직만 추가하는 방식으로 진행했다.

물론 AI가 만들어준 코드를 그대로 쓰지는 않았다. 항상 검토하고 우리 상황에 맞게 수정했는데, 그래도 시작점을 제공해주니까 개발 속도가 확실히 빨라졌다.

테스트 환경 구축

개발하면서 API 테스트가 필요했는데, MSW(Mock Service Worker)로 목업 API를 만들어서 개발했다. 실제 서버 없이도 모든 기능을 테스트할 수 있어서 개발 속도가 빨라졌다.

// MSW 핸들러 예시
export const authHandlers = [
  http.post("/api/auth/login/verification-codes", async ({ request }) => {
    const { mobile_number } = await request.json();
    return HttpResponse.json({
      issue_id: "test-issue-id",
      message: "인증번호가 전송되었습니다",
    });
  }),

  http.post(
    "/api/auth/login/verification-codes/verify",
    async ({ request }) => {
      const { code } = await request.json();
      if (code === "123456") {
        return HttpResponse.json({
          access_token: "mock-access-token",
          refresh_token: "mock-refresh-token",
        });
      }
      return new HttpResponse(null, { status: 400 });
    }
  ),
];

테스트도 Vitest로 57개를 작성해서 모든 핵심 기능이 제대로 동작하는지 확인할 수 있게 했다.

삽질했던 것들

Tauri 공부하느라 고생

Tauri v2가 나온 지 얼마 안 돼서 자료가 정말 부족했다. 스택오버플로우에도 질문이 별로 없고, 공식 문서만 믿고 가야 하는 상황이었다. 그래도 Rust 쪽은 따로 공부하고, 웹 쪽은 기존 지식을 활용해서 어떻게든 해냈다.

Windows vs macOS 차이점들

처음엔 macOS에서만 개발하다가 Windows에서 테스트해보니 보안 정책이 달라서 문제가 생겼다. 특히 파일 권한이나 네트워크 접근 부분에서 차이가 있어서, 런타임에 플랫폼을 감지해서 다르게 처리하도록 했다.

인증 시스템 재설계

처음엔 기존 iOS 앱의 SSO 시스템을 그대로 옮기려고 했는데, 너무 복잡해서 포기했다. 딥링크 방식으로 아예 새로 만드는 게 나았다.

결과적으로 어떻게 됐나

최종적으로 번들 크기는 80MB 정도로, Electron보다 70% 정도 작아졌다. 메모리 사용량도 평균 150MB 미만으로 유지되고 있고, 테스트는 57개 모두 통과하고 있다. 빌드 시간도 30초 안에 끝나서 개발할 때 스트레스가 없다.

배운 것들

Tauri는 정말 괜찮은 선택이었다. 네이티브 성능에 웹 기술의 편의성까지 챙길 수 있어서 최고였다. Zustand도 Redux보다 훨씬 간단하면서도 필요한 건 다 있어서 만족스러웠다.

데스크톱 앱에서 보안 구현하는 건 생각보다 복잡했다. 웹과는 다른 고려사항들이 많더라. 그래도 딥링크 기반 인증 시스템은 정말 잘 만든 것 같다.

릴리스 자동화는 정말 잘한 것 같다. 이제 실수할 일도 없고, 테스트 환경도 안정적이다. MSW로 목업 API 만든 것도 개발 속도를 많이 올려줬다.

마무리

이 프로젝트를 하면서 정말 많이 배웠다. 단순한 포팅이 아니라 완전히 새로운 플랫폼에서 혁신을 해본 경험이었다.

특히 딥링크 기반 인증 시스템과 포괄적인 보안 시스템을 구축한 게 가장 뿌듯하다. 사용자들이 안전하고 편리하게 학습할 수 있는 환경을 만들 수 있었거든.

2주라는 짧은 시간 안에 이런 퀄리티의 앱을 만들 수 있었던 건 기존 iOS 앱의 탄탄한 기반과 AI 도구의 도움, 그리고 Tauri 같은 모던한 기술 스택 덕분이었다. 혼자서 처음부터 설계했다면 몇 달은 걸렸을 것 같다.

Tauri라는 새로운 기술 스택도 경험해볼 수 있었고, 자동화의 중요성도 다시 한 번 느꼈다. 앞으로도 사용자가 느끼지 못할 정도로 자연스러운 기술을 만들어 나가고 싶다.


기술 스택

분야 기술 버전
Frontend React + TypeScript 19.1.0
Desktop Tauri v2.7.0
상태관리 Zustand 4.5.7
스타일링 Tailwind CSS 3.4.7
빌드 Vite 7.1.3
테스트 Vitest + MSW 3.2.4
Analytics Mixpanel + Sentry -

프로젝트 구조

solve-desktop/
├── src/
│   ├── components/           # React 컴포넌트
│   │   ├── auth/            # 인증 관련
│   │   ├── security/        # 보안 시스템
│   │   └── common/          # 공통 컴포넌트
│   ├── services/            # API 및 비즈니스 로직
│   │   ├── authService.ts   # 인증 서비스
│   │   ├── solveBooksLoginService.ts # 딥링크 로그인
│   │   ├── securityService.ts # 보안 서비스
│   │   └── analyticsService.ts # 분석 서비스
│   ├── store/               # 상태 관리 (Zustand)
│   └── __tests__/           # 테스트 + MSW 목업
├── src-tauri/               # Tauri 백엔드 (Rust)
├── scripts/                 # 자동화 스크립트
└── docs/                    # 기술 문서

이 프로젝트를 통해 모던한 데스크톱 앱 개발의 재미를 느꼈고, 많은 개발자들에게 도움이 되는 경험을 공유할 수 있어서 기쁘다! 🚀

Updated: