무료 Brat 생성기 제작 – Next.js 성능에 대해 배운 점

발행: (2026년 5월 22일 AM 04:46 GMT+9)
8 분 소요
원문: Dev.to

출처: Dev.to

I Built a Free Brat Generator – Here’s What I Learned About Next.js Performance published의 커버 이미지

Ibrat generator


brat aesthetic는 디자인이 단순합니다 — 굵은 소문자 텍스트, 단색 배경, 그 외는 없습니다. 그 단순함을 중심으로 도구를 만들다 보니 예상보다 더 흥미로운 결과를 얻게 되었습니다.

저는 ibratgenerator.com을 만들었습니다 — Charli XCX의 앨범 미학에서 영감을 받은 무료 brat‑스타일 이미지 생성기입니다. 이번 제작을 통해 Next.js 성능, 캔버스 렌더링, 그리고 SEO에 대해 배운 점을 정리해 보았습니다.

도구가 하는 일

사용자는 페이지를 열어 텍스트를 입력하고, 배경 색을 선택한 뒤 고해상도 PNG를 다운로드합니다. 회원가입, 워터마크, 계정이 전혀 필요 없습니다. 핵심 스택은 Next.js 16 App Router와 바닐라 TypeScript로 작성된 캔버스 기반 렌더링 엔진입니다.

도구가 지원하는 기능:

  • 사용자 지정 배경 및 텍스트 색
  • 비율 프리셋 (1:1, 4:5, 9:16, 16:9)
  • 스티커와 이모지 오버레이
  • 타이포그래피 제어 — 폰트 크기, 자간, 정렬
  • 3000 px까지 PNG 내보내기
  • 완전한 모바일 터치 지원

Next.js 동적 임포트 문제

캔버스 컴포넌트는 완전히 클라이언트‑사이드에서 동작합니다 — 서버에서는 존재하지 않는 브라우저 API를 사용하기 때문이죠. 그래서 next/dynamicssr: false 옵션으로 로드했습니다:

const BratGeneratorLazy = dynamic(
  () => import('./BratGenerator'),
  { ssr: false }
);

모바일에서는 잘 동작했습니다. 하지만 데스크톱에서는 Google이 페이지를 크롤링할 때 도구가 있어야 할 큰 빈 공간을 보게 되었습니다. LCP 요소에 예약된 공간이 없었기 때문에 컴포넌트가 로드될 때 레이아웃이 이동했습니다.

해결 방법

예약된 공간을 확보하는 컨테이너로 레이지 컴포넌트를 감싸세요:


  

position: relativewidth: 100%가 핵심입니다 — 이들이 없으면 스태킹 컨텍스트가 깨지고 스크롤 시 컴포넌트가 고정 헤더와 겹칠 수 있습니다. 이 한 줄 수정만으로 Google Search Console에서 데스크톱 위치 문제가 크게 개선되었습니다.


캔버스 성능 — 히스토리 스냅샷

도구는 undo/redo를 지원합니다. 사용자의 모든 상호작용은 히스토리 배열에 상태 스냅샷으로 푸시됩니다. 초기 구현에서는 매 스냅샷마다 배경 이미지를 복제했었습니다:

// Before — wrong
bgImage: s.bgImage
  ? (() => {
      const img = new Image();
      img.src = s.bgImage!.src;
      return img;
    })()
  : null,

키 입력마다 새로운 HTMLImageElement를 생성하면 힙이 급격히 변하고 UI 스레드가 끊깁니다 — 특히 모바일에서 눈에 띕니다.

해결 방법

이미지를 복제하지 말고 기존 이미지를 참조하세요:

// After — correct
bgImage: s.bgImage,

이미지 객체는 스냅샷 간에 정적이므로, 레퍼런스를 복사하는 것만으로도 안전하고 매 상호작용마다 불필요한 DOM 인스턴스 생성을 없앨 수 있습니다.


포인터 이벤트 정리

스티커 드래그 처리를 위해 window에 전역 포인터 리스너를 추가했습니다:

window.addEventListener('pointermove', onPointerMove);
window.addEventListener('pointerup', onPointerUp);
window.addEventListener('pointercancel', onPointerUp);

React StrictMode에서는 개발 환경에서 컴포넌트가 두 번 마운트됩니다. 리스너를 추가하기 전에 명시적으로 정리하지 않으면 중복 핸들러가 생겨 드래그 해제 시 두 번 트리거되는 버그가 발생합니다.

해결 방법

새 리스너를 추가하기 전에 기존 리스너를 제거하세요:

// Remove before adding to prevent duplicates
window.removeEventListener('pointermove', onPointerMove);
window.removeEventListener('pointerup', onPointerUp);
window.removeEventListener('pointercancel', onPointerUp);

window.addEventListener('pointermove', onPointerMove);
window.addEventListener('pointerup', onPointerUp);
window.addEventListener('pointercancel', onPointerUp);

Next.js와 CSP

next.config.ts에 CSP 헤더를 추가했더니 Microsoft Clarity가 깨졌습니다. 스크립트는 scripts.clarity.ms에서 로드되지만 데이터는 t.clarity.ms로 전송되는데, 두 서브도메인 모두 허용 리스트에 포함시켜야 했기 때문입니다:

// script-src needs scripts.clarity.ms
// connect-src needs t.clarity.ms
// Both subdomains required — just clarity.ms is not enough

교훈: CSP 헤더를 추가한 뒤 DevTools의 네트워크 탭을 반드시 확인하세요. 콘솔 오류 메시지는 차단된 정확한 도메인을 알려줍니다.


다르게 했으면 좋았을 점

초기에 추가한 다국어 라우팅(/[lang]/ 동적 세그먼트) 때문에 라우트를 삭제한 뒤 .next 캐시를 비우지 않아 TypeScript 빌드 오류가 발생했습니다. .next/dev/types/validator.ts에 남아 있던 오래된 타입이 삭제된 라우트를 참조하고 있었던 것이죠.

해결 방법: .next 디렉터리를 완전히 삭제하고 새로 빌드합니다. 간단하지만 구조적인 라우팅 변경을 할 때마다 캐시를 비우면 디버깅 시간을 크게 줄일 수 있습니다.


도구가 라이브 중

Brat Generator — 무료, 회원가입·워터마크 없이 PNG 내보내기. 모바일과 데스크톱 모두에서 동작합니다.

Next.js에서 캔버스 기반 도구를 만들고 있다면 동적 임포트 + 예약 공간 패턴을 툴킷에 넣어두세요. 그 단일 래퍼 “ 로 인한 LCP 개선 효과는 기대 이상이었습니다.

Next.js 16, TypeScript, 바닐라 캔버스로 제작되었습니다.

  • Next.js 16, TypeScript, 그리고 바닐라 Canvas API. Vercel에 배포되었습니다.*
0 조회
Back to Blog

관련 글

더 보기 »