Nuxt에서 이미지 압축하는 방법: 브라우저 기반 JPG PNG WEBP 압축 구현

Nuxt 프로젝트에서 이미지 압축 기능을 구현하는 방법을 npm, yarn 설치부터 파일 업로드, 브라우저 압축, 미리보기, 다운로드 처리까지 개발자 관점에서 자세히 정리합니다.

Nuxt 이미지 압축 기능 구현하기: npm, yarn 설치부터 실제 사용까지

Nuxt 이미지 압축 기능은 블로그, 쇼핑몰, 관리자 페이지, 프로필 업로드, 게시판 첨부파일 기능을 만들 때 자주 필요한 기능입니다. 사용자가 업로드하는 JPG, PNG, WEBP 이미지의 용량이 너무 크면 페이지 로딩 속도가 느려지고, 서버 저장 공간과 트래픽 비용도 증가합니다. 이 글에서는 Nuxt 프로젝트에서 이미지 압축 기능을 처음 설치하는 단계부터 npm 또는 yarn을 이용한 패키지 설치, 이미지 선택, 압축 처리, 미리보기, 다운로드 기능까지 개발자 입장에서 자세히 정리합니다.

이미지 압축 기능을 직접 만들기 전에 참고할 수 있는 온라인 도구로는 다플 이미지 압축 도구가 있습니다. 이 도구는 JPG, PNG, WEBP 파일을 지원하고, 여러 장을 한 번에 선택할 수 있으며, 서버 업로드 없이 브라우저 안에서 이미지를 처리하는 구조입니다. 웹 게시용 이미지는 WEBP 80%와 긴 변 1920px 설정을 권장하고 있습니다.

이미지 압축이 필요한 이유

웹서비스에서 이미지 용량은 생각보다 큰 성능 병목이 됩니다. 특히 사용자가 직접 이미지를 올리는 서비스에서는 개발자가 원본 이미지 크기를 통제하기 어렵습니다.

예를 들어 스마트폰으로 촬영한 사진 한 장은 3MB에서 10MB 이상이 될 수 있습니다. 이런 이미지를 그대로 게시글 썸네일, 상품 이미지, 프로필 사진으로 사용하면 다음과 같은 문제가 발생합니다.

문제설명

페이지 로딩 속도 저하 이미지 파일이 클수록 LCP 성능이 나빠질 수 있습니다.
서버 저장 공간 증가 원본 이미지를 그대로 저장하면 스토리지 비용이 증가합니다.
트래픽 비용 증가 CDN 또는 서버 전송량이 커집니다.
사용자 경험 저하 모바일 환경에서 이미지 표시가 느려집니다.
SEO 불리 느린 페이지는 검색 노출과 사용자 이탈률에 영향을 줄 수 있습니다.

따라서 Nuxt 프로젝트에서는 이미지를 업로드하기 전 브라우저에서 압축하거나, 서버 업로드 후 백엔드에서 리사이즈 및 압축하는 방식을 함께 고려해야 합니다.

브라우저 이미지 압축과 서버 이미지 압축의 차이

이미지 압축 방식은 크게 두 가지입니다.

브라우저에서 이미지 압축

사용자의 브라우저에서 이미지 파일을 압축한 뒤 서버로 업로드하는 방식입니다.

장점은 서버 부하를 줄일 수 있고, 업로드 전에 파일 용량을 줄일 수 있다는 점입니다. 특히 관리자 페이지나 간단한 이미지 도구를 만들 때 적합합니다.

단점은 사용자의 브라우저 성능에 따라 처리 속도가 달라질 수 있고, 아주 큰 이미지를 여러 장 처리할 때 메모리 사용량이 증가할 수 있다는 점입니다.

서버에서 이미지 압축

사용자가 원본 이미지를 서버로 업로드한 뒤, 서버에서 Sharp, ImageMagick 같은 라이브러리로 압축하는 방식입니다.

장점은 압축 품질과 결과물을 서버에서 일관되게 제어할 수 있다는 점입니다. 단점은 원본 파일이 먼저 서버로 업로드되기 때문에 트래픽과 서버 리소스가 더 필요합니다.

실무에서는 보통 다음과 같이 사용합니다.

상황추천 방식

간단한 이미지 압축 도구 브라우저 압축
프로필 이미지 업로드 브라우저 압축 + 서버 검증
쇼핑몰 상품 이미지 서버 압축 권장
대량 이미지 업로드 브라우저 선압축 + 서버 후처리
보안이 중요한 서비스 서버 검증 필수

이 글에서는 Nuxt에서 브라우저 기반 이미지 압축 기능을 구현하는 방법을 중심으로 설명합니다.

Nuxt 프로젝트 생성하기

먼저 Nuxt 프로젝트를 새로 생성합니다.

npm 사용

npx nuxi@latest init nuxt-image-compressor
cd nuxt-image-compressor
npm install
npm run dev

yarn 사용

yarn dlx nuxi@latest init nuxt-image-compressor
cd nuxt-image-compressor
yarn install
yarn dev

개발 서버가 실행되면 기본적으로 다음 주소에서 확인할 수 있습니다.

http://localhost:3000

이미지 압축 패키지 설치하기

브라우저에서 이미지를 압축하기 위해 browser-image-compression 패키지를 사용할 수 있습니다.

npm 설치

npm install browser-image-compression

yarn 설치

yarn add browser-image-compression

이 패키지는 브라우저 환경에서 이미지 파일을 압축할 수 있게 해줍니다. Nuxt에서는 서버 사이드 렌더링 환경도 함께 고려해야 하므로, 이미지 압축 코드는 반드시 클라이언트에서만 실행되도록 작성하는 것이 좋습니다.

Nuxt에서 이미지 압축 페이지 만들기

pages/image-compressor.vue 파일을 생성합니다.

<template>
  <main class="page">
    <section class="compressor">
      <h1>Nuxt 이미지 압축</h1>
      <p>
        JPG, PNG, WEBP 이미지를 선택하면 브라우저에서 압축한 뒤
        압축 전후 용량을 비교할 수 있습니다.
      </p>

      <div class="upload-box">
        <input
          type="file"
          accept="image/jpeg,image/png,image/webp"
          multiple
          @change="handleFileChange"
        />
      </div>

      <div class="option-box">
        <label>
          최대 파일 크기(MB)
          <input
            v-model.number="options.maxSizeMB"
            type="number"
            min="0.1"
            step="0.1"
          />
        </label>

        <label>
          최대 가로/세로 길이(px)
          <input
            v-model.number="options.maxWidthOrHeight"
            type="number"
            min="100"
            step="100"
          />
        </label>

        <label>
          Web Worker 사용
          <input
            v-model="options.useWebWorker"
            type="checkbox"
          />
        </label>
      </div>

      <div v-if="loading" class="loading">
        이미지 압축 중입니다.
      </div>

      <div v-if="compressedImages.length" class="result-list">
        <article
          v-for="item in compressedImages"
          :key="item.id"
          class="result-card"
        >
          <div class="preview">
            <img :src="item.previewUrl" :alt="item.originalName" />
          </div>

          <div class="info">
            <h2>{{ item.originalName }}</h2>

            <p>원본 용량: {{ formatFileSize(item.originalSize) }}</p>
            <p>압축 용량: {{ formatFileSize(item.compressedSize) }}</p>
            <p>감소율: {{ item.reductionRate }}%</p>

            <a
              :href="item.downloadUrl"
              :download="item.downloadName"
              class="download-button"
            >
              압축 이미지 다운로드
            </a>
          </div>
        </article>
      </div>
    </section>
  </main>
</template>

<script setup lang="ts">
import imageCompression from 'browser-image-compression'

type CompressedImage = {
  id: string
  originalName: string
  originalSize: number
  compressedSize: number
  reductionRate: number
  previewUrl: string
  downloadUrl: string
  downloadName: string
}

const loading = ref(false)

const options = reactive({
  maxSizeMB: 1,
  maxWidthOrHeight: 1920,
  useWebWorker: true
})

const compressedImages = ref<CompressedImage[]>([])

const handleFileChange = async (event: Event) => {
  const input = event.target as HTMLInputElement

  if (!input.files || input.files.length === 0) {
    return
  }

  loading.value = true
  compressedImages.value = []

  try {
    const files = Array.from(input.files)

    for (const file of files) {
      if (!file.type.startsWith('image/')) {
        continue
      }

      const compressedFile = await imageCompression(file, {
        maxSizeMB: options.maxSizeMB,
        maxWidthOrHeight: options.maxWidthOrHeight,
        useWebWorker: options.useWebWorker
      })

      const previewUrl = URL.createObjectURL(compressedFile)
      const downloadUrl = URL.createObjectURL(compressedFile)

      const reductionRate = Math.round(
        ((file.size - compressedFile.size) / file.size) * 100
      )

      compressedImages.value.push({
        id: crypto.randomUUID(),
        originalName: file.name,
        originalSize: file.size,
        compressedSize: compressedFile.size,
        reductionRate,
        previewUrl,
        downloadUrl,
        downloadName: createDownloadName(file.name)
      })
    }
  } catch (error) {
    console.error('이미지 압축 중 오류가 발생했습니다.', error)
    alert('이미지 압축 중 오류가 발생했습니다.')
  } finally {
    loading.value = false
    input.value = ''
  }
}

const createDownloadName = (fileName: string) => {
  const dotIndex = fileName.lastIndexOf('.')

  if (dotIndex === -1) {
    return `${fileName}-compressed`
  }

  const name = fileName.slice(0, dotIndex)
  const ext = fileName.slice(dotIndex)

  return `${name}-compressed${ext}`
}

const formatFileSize = (size: number) => {
  if (size < 1024) {
    return `${size} B`
  }

  if (size < 1024 * 1024) {
    return `${(size / 1024).toFixed(1)} KB`
  }

  return `${(size / 1024 / 1024).toFixed(2)} MB`
}
</script>

<style scoped>
.page {
  min-height: 100vh;
  padding: 40px 20px;
  background: #f6f8fb;
}

.compressor {
  max-width: 960px;
  margin: 0 auto;
  padding: 32px;
  background: #fff;
  border-radius: 16px;
  box-shadow: 0 8px 30px rgba(15, 23, 42, 0.08);
}

.compressor h1 {
  margin-bottom: 12px;
  font-size: 32px;
}

.upload-box {
  margin: 24px 0;
  padding: 32px;
  border: 2px dashed #cbd5e1;
  border-radius: 12px;
  background: #f8fafc;
}

.option-box {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
  margin-bottom: 24px;
}

.option-box label {
  display: flex;
  flex-direction: column;
  gap: 8px;
  font-size: 14px;
  color: #334155;
}

.option-box input[type='number'] {
  padding: 10px;
  border: 1px solid #cbd5e1;
  border-radius: 8px;
}

.loading {
  margin: 20px 0;
  color: #2563eb;
  font-weight: 600;
}

.result-list {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.result-card {
  display: grid;
  grid-template-columns: 220px 1fr;
  gap: 20px;
  padding: 20px;
  border: 1px solid #e2e8f0;
  border-radius: 12px;
}

.preview img {
  width: 100%;
  height: 160px;
  object-fit: contain;
  background: #f1f5f9;
  border-radius: 8px;
}

.info h2 {
  margin-top: 0;
  font-size: 18px;
}

.download-button {
  display: inline-block;
  margin-top: 12px;
  padding: 10px 14px;
  color: #fff;
  background: #2563eb;
  border-radius: 8px;
  text-decoration: none;
}

@media (max-width: 768px) {
  .option-box {
    grid-template-columns: 1fr;
  }

  .result-card {
    grid-template-columns: 1fr;
  }
}
</style>

코드 구조 설명

위 예제는 Nuxt에서 이미지 압축 기능을 구현할 때 가장 기본이 되는 구조입니다.

핵심 흐름은 다음과 같습니다.

  1. 사용자가 이미지 파일을 선택합니다.
  2. handleFileChange 함수가 파일 목록을 읽습니다.
  3. browser-image-compression으로 이미지를 압축합니다.
  4. 압축 전후 파일 크기를 비교합니다.
  5. URL.createObjectURL()로 미리보기와 다운로드 링크를 생성합니다.
  6. 사용자는 압축된 이미지를 다운로드합니다.

이미지 압축 옵션 이해하기

const options = reactive({
  maxSizeMB: 1,
  maxWidthOrHeight: 1920,
  useWebWorker: true
})

maxSizeMB

압축 후 목표 파일 크기입니다.

maxSizeMB: 1

위 설정은 압축된 이미지가 최대한 1MB 안쪽으로 줄어들도록 시도합니다. 다만 이미지 종류, 해상도, 원본 품질에 따라 정확히 1MB 이하가 보장되지 않을 수 있습니다.

maxWidthOrHeight

이미지의 긴 변 기준 최대 크기입니다.

maxWidthOrHeight: 1920

예를 들어 원본 이미지가 4000x3000이라면 긴 변을 1920px로 줄이면서 비율을 유지합니다. 웹 게시용 이미지는 보통 1920px 정도면 충분한 경우가 많습니다.

useWebWorker

이미지 압축 작업을 Web Worker에서 처리할지 여부입니다.

useWebWorker: true

이미지 압축은 브라우저에서 비교적 무거운 작업입니다. Web Worker를 사용하면 메인 UI 스레드가 덜 막히기 때문에 사용자가 페이지가 멈춘 것처럼 느끼는 상황을 줄일 수 있습니다.

여러 장 이미지 압축하기

위 예제에서는 multiple 속성을 사용했습니다.

<input
  type="file"
  accept="image/jpeg,image/png,image/webp"
  multiple
  @change="handleFileChange"
/>

이렇게 하면 사용자가 여러 이미지를 한 번에 선택할 수 있습니다. 다만 실무에서는 무제한 업로드를 허용하면 브라우저 메모리 문제가 생길 수 있으므로 개수 제한을 두는 것이 좋습니다.

예를 들어 최대 20개까지만 허용하려면 다음 코드를 추가할 수 있습니다.

const files = Array.from(input.files)

if (files.length > 20) {
  alert('이미지는 최대 20개까지 선택할 수 있습니다.')
  loading.value = false
  input.value = ''
  return
}

다플 이미지 압축 도구도 최대 20개까지 일괄 압축할 수 있는 구조를 제공하고 있으므로, 비슷한 사용자 경험을 구현하려면 20개 제한은 현실적인 기준이 될 수 있습니다.

파일당 최대 용량 제한하기

사용자가 너무 큰 이미지를 선택하면 브라우저 압축 과정에서 속도가 느려질 수 있습니다. 따라서 파일당 최대 용량을 제한하는 것이 좋습니다.

const MAX_FILE_SIZE = 30 * 1024 * 1024

const validFiles = files.filter((file) => {
  if (file.size > MAX_FILE_SIZE) {
    alert(`${file.name} 파일은 30MB를 초과했습니다.`)
    return false
  }

  return true
})

30MB 제한은 이미지 압축 도구를 만들 때 사용자가 이해하기 쉬운 기준입니다. 원본 이미지가 30MB를 넘는 경우에는 브라우저에서 처리하기보다 서버 처리 또는 사전 리사이즈를 안내하는 것이 좋습니다.

PNG 압축 시 주의할 점

JPG와 WEBP는 손실 압축을 통해 용량을 크게 줄일 수 있습니다. 반면 PNG는 투명 배경을 지원하고 무손실 이미지에 가까운 특성이 있어, 품질 값을 조절해도 기대만큼 용량이 줄지 않을 수 있습니다.

실무에서는 다음 기준을 사용하면 좋습니다.

원본 형식추천 처리 방식

JPG 품질 조절 및 리사이즈
PNG 투명 배경이 필요하면 PNG 유지
PNG 투명 배경이 필요 없으면 WEBP 변환 고려
WEBP 품질 조절 및 리사이즈
썸네일 WEBP 권장
원본 보관용 원본 별도 저장 고려

특히 블로그나 웹 게시용 이미지는 WEBP 형식이 유리한 경우가 많습니다. 다만 사용자가 업로드한 원본 확장자를 그대로 유지할지, WEBP로 변환할지는 서비스 정책에 따라 결정해야 합니다.

Nuxt에서 클라이언트 전용 처리 주의하기

Nuxt는 서버 사이드 렌더링을 지원합니다. 따라서 브라우저 객체인 window, document, File, URL.createObjectURL() 등을 서버에서 실행하면 오류가 발생할 수 있습니다.

이미지 압축 기능은 파일 선택 이벤트 이후 브라우저에서만 실행되므로 일반적으로 문제는 적지만, 플러그인으로 분리하거나 컴포저블로 만들 때는 클라이언트 실행 여부를 확인하는 것이 좋습니다.

if (!process.client) {
  return
}

또는 컴포넌트를 클라이언트에서만 렌더링하려면 다음처럼 사용할 수 있습니다.

<ClientOnly>
  <ImageCompressor />
</ClientOnly>

컴포넌트로 분리하기

실무 프로젝트에서는 페이지에 모든 코드를 넣기보다 컴포넌트로 분리하는 것이 좋습니다.

components/ImageCompressor.vue 파일을 생성합니다.

<template>
  <section class="image-compressor">
    <input
      type="file"
      accept="image/jpeg,image/png,image/webp"
      multiple
      @change="handleFileChange"
    />

    <p v-if="loading">압축 중입니다.</p>

    <ul>
      <li v-for="image in compressedImages" :key="image.id">
        <img :src="image.previewUrl" :alt="image.originalName" width="160" />
        <p>{{ image.originalName }}</p>
        <p>
          {{ formatFileSize(image.originalSize) }}
          →
          {{ formatFileSize(image.compressedSize) }}
        </p>
        <a :href="image.downloadUrl" :download="image.downloadName">
          다운로드
        </a>
      </li>
    </ul>
  </section>
</template>

<script setup lang="ts">
import imageCompression from 'browser-image-compression'

const loading = ref(false)
const compressedImages = ref<any[]>([])

const handleFileChange = async (event: Event) => {
  const input = event.target as HTMLInputElement

  if (!input.files) {
    return
  }

  loading.value = true

  try {
    const files = Array.from(input.files)

    const result = await Promise.all(
      files.map(async (file) => {
        const compressedFile = await imageCompression(file, {
          maxSizeMB: 1,
          maxWidthOrHeight: 1920,
          useWebWorker: true
        })

        return {
          id: crypto.randomUUID(),
          originalName: file.name,
          originalSize: file.size,
          compressedSize: compressedFile.size,
          previewUrl: URL.createObjectURL(compressedFile),
          downloadUrl: URL.createObjectURL(compressedFile),
          downloadName: createDownloadName(file.name)
        }
      })
    )

    compressedImages.value = result
  } finally {
    loading.value = false
    input.value = ''
  }
}

const createDownloadName = (fileName: string) => {
  const dotIndex = fileName.lastIndexOf('.')

  if (dotIndex === -1) {
    return `${fileName}-compressed`
  }

  return `${fileName.slice(0, dotIndex)}-compressed${fileName.slice(dotIndex)}`
}

const formatFileSize = (size: number) => {
  return `${(size / 1024 / 1024).toFixed(2)} MB`
}
</script>

그리고 페이지에서 불러옵니다.

<template>
  <main>
    <h1>이미지 압축 도구</h1>

    <ClientOnly>
      <ImageCompressor />
    </ClientOnly>
  </main>
</template>

압축한 이미지를 서버로 업로드하기

이미지 압축 후 다운로드만 제공하는 도구라면 여기서 끝나도 됩니다. 하지만 실제 서비스에서는 압축한 이미지를 서버로 업로드해야 하는 경우가 많습니다.

const uploadCompressedImage = async (file: File) => {
  const formData = new FormData()
  formData.append('image', file)

  const response = await fetch('/api/upload-image', {
    method: 'POST',
    body: formData
  })

  if (!response.ok) {
    throw new Error('이미지 업로드에 실패했습니다.')
  }

  return await response.json()
}

압축 후 바로 업로드하려면 다음처럼 사용할 수 있습니다.

const compressedFile = await imageCompression(file, {
  maxSizeMB: 1,
  maxWidthOrHeight: 1920,
  useWebWorker: true
})

await uploadCompressedImage(compressedFile)

Nuxt 서버 API 예제

Nuxt 3에서는 server/api 디렉터리에 서버 API를 만들 수 있습니다. 다만 파일 업로드 처리는 프로젝트 구성에 따라 formidable, multer, S3 Presigned URL 방식 등을 사용할 수 있습니다.

실무에서는 Nuxt 서버에 직접 저장하기보다 S3, Cloudflare R2, NCP Object Storage 같은 오브젝트 스토리지에 업로드하는 방식을 많이 사용합니다.

간단한 흐름은 다음과 같습니다.

// server/api/upload-image.post.ts
export default defineEventHandler(async (event) => {
  return {
    success: true,
    message: '실제 프로젝트에서는 multipart 파싱 후 스토리지에 저장합니다.'
  }
})

실제 운영 환경에서는 다음 항목을 반드시 검증해야 합니다.

검증 항목설명

MIME 타입 image/jpeg, image/png, image/webp만 허용
파일 크기 업로드 가능한 최대 용량 제한
확장자 허용된 확장자만 저장
파일명 원본 파일명 그대로 사용하지 않기
악성 파일 이미지로 위장한 파일 차단
저장 경로 사용자 입력값으로 경로 생성 금지

이미지 압축 품질 기준 추천

이미지 압축 기능을 만들 때 가장 많이 고민하는 부분은 품질과 용량의 균형입니다.

용도추천 설정

블로그 본문 이미지 WEBP, 80%, 긴 변 1920px
썸네일 이미지 WEBP, 75~80%, 800~1200px
프로필 이미지 JPG 또는 WEBP, 512px
상품 상세 이미지 WEBP, 80~85%, 1920px
관리자 첨부 이미지 1~2MB 제한
원본 보관 필요 원본 저장 + 압축본 별도 생성

블로그나 일반 웹 게시용 이미지는 긴 변을 1920px로 줄이고 WEBP 품질을 80% 정도로 맞추면 용량과 화질의 균형이 좋은 편입니다.

이미지 압축이 필요하다면 다플 이미지 압축 도구를 함께 사용하면 개발 전 기준을 잡는 데 도움이 됩니다.

내부링크 예시:
이미지 용량을 빠르게 줄이고 싶다면 [이미지 압축 도구]를 사용해 JPG, PNG, WEBP 파일을 브라우저에서 바로 압축할 수 있습니다.

이미지 압축 기능에서 자주 발생하는 오류

1. window is not defined 오류

Nuxt에서 서버 사이드 렌더링 중 브라우저 전용 코드가 실행되면 발생합니다.

해결 방법은 이미지 압축 컴포넌트를 <ClientOnly>로 감싸는 것입니다.

<ClientOnly>
  <ImageCompressor />
</ClientOnly>

또는 코드 실행 전에 클라이언트 여부를 확인합니다.

if (!process.client) {
  return
}

2. 파일을 선택했는데 다시 같은 파일 선택이 안 되는 문제

브라우저의 file input은 같은 파일을 다시 선택하면 change 이벤트가 발생하지 않을 수 있습니다.

해결 방법은 처리 후 input 값을 비워주는 것입니다.

input.value = ''

3. PNG 용량이 거의 줄지 않는 문제

PNG는 JPG나 WEBP처럼 품질을 낮춰서 크게 줄이는 방식이 잘 맞지 않을 수 있습니다. 투명 배경이 필요 없다면 WEBP 변환을 고려하는 것이 좋습니다.

4. 대용량 이미지에서 브라우저가 느려지는 문제

이미지 압축은 CPU와 메모리를 사용하는 작업입니다. 여러 장의 대용량 이미지를 동시에 처리하면 브라우저가 느려질 수 있습니다.

해결 방법은 다음과 같습니다.

if (files.length > 20) {
  alert('이미지는 최대 20개까지 압축할 수 있습니다.')
  return
}

또한 파일당 최대 용량 제한을 두는 것이 좋습니다.

const MAX_FILE_SIZE = 30 * 1024 * 1024

if (file.size > MAX_FILE_SIZE) {
  alert('30MB 이하 이미지만 업로드할 수 있습니다.')
  return
}

이미지 압축 기능을 만들 때 SEO 관점에서 고려할 점

이미지 압축은 단순히 파일 용량을 줄이는 기능이 아니라 SEO에도 영향을 줍니다.

이미지 용량이 줄어들면 페이지 로딩 속도가 개선될 수 있고, 사용자가 콘텐츠를 더 빠르게 볼 수 있습니다. 특히 블로그, 랜딩 페이지, 쇼핑몰처럼 이미지가 많은 페이지에서는 이미지 최적화가 중요합니다.

개발자가 함께 고려해야 할 항목은 다음과 같습니다.

항목설명

파일명 의미 있는 영문 파일명 사용
alt 속성 이미지 내용을 설명하는 대체 텍스트 작성
width, height 레이아웃 밀림 방지
lazy loading 화면 밖 이미지는 지연 로딩
WEBP 사용 지원 브라우저에서는 용량 절감
썸네일 분리 목록 페이지에는 작은 이미지 사용

Nuxt에서는 이미지 렌더링 시 다음처럼 alt 값을 반드시 넣는 것이 좋습니다.

<img
  src="/images/nuxt-image-compression.webp"
  alt="Nuxt 이미지 압축 기능 구현 예제"
/>

FAQ

Nuxt에서 이미지 압축은 프론트엔드에서 하는 것이 좋나요?

간단한 이미지 도구나 업로드 전 용량 감소 목적이라면 프론트엔드 압축이 좋습니다. 하지만 보안 검증과 최종 이미지 품질 관리는 서버에서도 한 번 더 처리하는 것이 안전합니다.

npm과 yarn 중 어떤 것을 사용해야 하나요?

프로젝트에서 이미 사용 중인 패키지 매니저를 따르면 됩니다. 기존 프로젝트가 npm 기반이면 npm을 사용하고, yarn 기반이면 yarn을 사용하면 됩니다.

이미지 압축 후 화질이 많이 깨질 수 있나요?

압축률을 너무 높이면 화질 저하가 발생할 수 있습니다. 일반적인 웹 게시용 이미지는 긴 변 1920px, 품질 80% 정도를 기준으로 테스트하는 것이 좋습니다.

PNG도 압축이 잘 되나요?

PNG는 JPG나 WEBP보다 용량이 크게 줄지 않을 수 있습니다. 투명 배경이 필요 없다면 WEBP 변환을 고려하는 것이 좋습니다.

서버 업로드 없이 이미지 압축이 가능한가요?

가능합니다. 브라우저 기반 이미지 압축은 사용자의 기기에서 파일을 처리하므로 서버 업로드 없이 압축 결과를 만들 수 있습니다.

마무리

Nuxt 이미지 압축 기능은 사용자 업로드가 있는 웹서비스에서 성능, 저장 공간, 트래픽, SEO를 함께 개선할 수 있는 실용적인 기능입니다. npm 또는 yarn으로 browser-image-compression 패키지를 설치하면 Nuxt 프로젝트에서도 비교적 간단하게 JPG, PNG, WEBP 이미지 압축 기능을 구현할 수 있습니다.

실무에서는 브라우저에서 먼저 이미지를 압축하고, 서버에서는 MIME 타입, 파일 크기, 확장자, 저장 경로를 다시 검증하는 구조를 추천합니다. 블로그나 웹 게시용 이미지는 WEBP 80%, 긴 변 1920px 정도를 기준으로 시작하면 품질과 용량 사이의 균형을 맞추기 좋습니다.

추천 태그

Nuxt 이미지 압축,이미지 압축,사진 용량 줄이기,Nuxt 업로드,Vue 이미지 압축,npm 이미지 압축,yarn 이미지 압축,WEBP 압축,JPG 압축,PNG 압축,프론트엔드 이미지 최적화,브라우저 이미지 압축,웹 성능 최적화,SEO 이미지 최적화,Nuxt 개발