오늘은 개발자라면 누구나 겪어봤을 “내 컴퓨터에선 되는데…” 상황을 해결하는 날이었습니다.

스토리의 선택지 버튼이 갑자기 작동하지 않는 치명적인 버그가 발생했습니다. 클릭해도 아무 반응이 없고, 에러 메시지도 뜨지 않는 답답한 상황이었죠.

유령 스크립트 잡기

원인은 스크립트를 처리하는 방식에 있었습니다. 우리는 게임 로직(선택 처리, 점수 계산 등)을 HTML 콘텐츠 안에 직접 주입하고 있었습니다.

Astro에서 set:html을 사용해 HTML을 주입할 때, 그 안에 포함된 <script> 태그는 우리가 기대한 대로 동작하지 않을 때가 많습니다. 보안상의 이유로 실행이 차단되거나, 조작하려는 요소가 생성되기도 전에 먼저 실행되어 버리기도 하죠.

해결책은 스크립트를 억지로 실행시키는 것이 아니라, 위치를 옮기는 것이었습니다.

불안정한 인라인 스크립트 대신, Astro 컴포넌트 내부의 견고한 클라이언트 사이드 스크립트로 로직을 옮겼습니다. 또한, 서로 다른 스크립트들이 소통할 수 있도록 window 전역 객체에 함수를 명시적으로 연결했습니다.

// 이전: 스크립트가 알아서 실행되길 기대함 (실패!)
calculateAndDisplayPersonality(data); // ReferenceError 발생

// 이후: window 객체를 통해 명시적으로 호출
if (typeof window.calculateAndDisplayPersonality === 'function') {
  window.calculateAndDisplayPersonality(data);
}

‘암시적인 것보다 명시적인 것이 낫다’는 프로그래밍의 격언을 다시 한번 깨닫는 순간이었습니다.

새로운 시작을 위하여

로직을 수정하던 중, 또 다른 UX 문제를 발견했습니다.

사용자가 ‘프랑켄슈타인’을 플레이한 뒤 바로 ‘오만과 편견’을 시작하면, 이전 스토리의 선택 기록이 브라우저에 그대로 남아있었습니다. 즉, 과학적 호기심 점수가 낭만적인 로맨스 결과에 엉뚱한 영향을 줄 수 있었던 거죠.

그래서 세션 초기화 기능을 추가했습니다.

이제 표지에서 “여행 시작하기” 버튼을 누르면, 이전의 모든 기록이 깨끗하게 지워집니다.

startButton.addEventListener('click', () => {
  sessionStorage.removeItem('userChoices');
  // 새로운 이야기를 위한 깨끗한 시작
});

작은 변화지만, 덕분에 사용자는 매번 완전히 새로운 모험을 떠날 수 있게 되었습니다.

드디어 속도 개선에 손대다

버그를 고치고 나서, 미뤄왔던 작업을 드디어 시작했습니다. 바로 이미지 최적화입니다.

우리 일러스트레이션은 아름답지만, 용량이 너무 컸습니다. 109개의 PNG 파일이 총 215MB나 되었고, 이 파일들이 public/ 폴더에 그대로 있었습니다. 압축도 없고, 최신 포맷도 아니고, 반응형 크기 조정도 없이 말이죠.

모바일에서도 1200x800 PNG를 원본 그대로 다운로드하고 있었던 겁니다.

Astro의 내장 Image 컴포넌트와 Vercel의 Image Optimization API를 조합해서 해결했습니다:

// 이전: 평범한 img 태그
<img src="/stories/jekyll-and-hyde/opening_utterson.png" alt="스토리 장면" />

// 이후: 최적화되고, 반응형이며, 지연 로딩되는 이미지
<Image
  src={imageModule.default}
  alt="스토리 장면"
  width={1200}
  height={800}
  format="webp"
  quality={85}
  loading="lazy"
  decoding="async"
/>

작업 과정은 다음과 같았습니다:

  1. 마이그레이션: 모든 이미지를 public/에서 src/assets/로 이동 (Astro가 최적화할 수 있는 위치)
  2. 동적 임포트: import.meta.glob()를 사용해 스토리별로 이미지를 동적으로 로드
  3. 설정: 반응형 브레이크포인트 설정 [320, 640, 768, 1024, 1280, 1536]
  4. 포맷 변환: 브라우저 지원에 따라 자동으로 WebP/AVIF 제공

예상 결과 (배포 후):

  • 이미지 용량 70-85% 감소
  • 로딩 속도 3-5배 향상
  • 화면 아래 이미지 자동 지연 로딩
  • 반응형 크기 조정 (모바일은 모바일 크기로 받음)

가장 좋은 점은? 이 모든 것이 자동으로 이루어진다는 겁니다. 빌드 과정에서 이미지를 한 번 최적화하면, Vercel의 CDN이 각 사용자에게 적절한 포맷과 크기로 제공합니다.

이제 이미지 하나 로딩하는 데 5초씩 기다릴 필요가 없어졌습니다.

오늘 배운 것들

  1. 스크립트는 명시적인 스코프가 필요하다 - 헷갈리면 window에 붙이고 호출 전에 확인하자
  2. 세션 상태는 끈질기다 - 의도적으로 지우지 않으면 이상한 버그의 원인이 된다
  3. 이미지 최적화는 선택이 아니다 - 모바일 네트워크 사용자가 고마워할 것이다
  4. Astro의 Image 컴포넌트는 마법이다 - 진심으로, 처음부터 써야 했다

내일: 드디어 사람들한테 이걸 알리기 (일명 마케팅 시작)