# GLOBEAU 뷰티 플랫폼 이미지 포털 검색(SEO) 최적화 및 자동화 파이프라인 가이드

## 1. 개요 및 목적
* **핵심 목표**: 네이버, 구글, 다음 등 주요 검색포털 이미지 검색 탭에서 뷰티샵 시술 전후 사진, 프로그램, 제품 이미지를 상위 노출시키고, 클릭 시 GLOBEAU 플랫폼 내 해당 본문 상세페이지(예약/구매)로 트래픽을 직접 유입시키는 구조 구축.
* **해결 과제**:
  1. 무의미한 난수 파일명(`1729381029_a9f8.jpg`) 탈피 및 키워드 기반 URL/슬러그 확보.
  2. 모바일 로딩 성능 향상(WebP 자동 압축)을 통한 구글 코어 웹 바이탈 점수 획득.
  3. 관리자/원장님의 수동 입력 부담 없는 업로드 시점 메타데이터(`alt_text`, `seo_slug`, `tags` 등) 100% 자동 생성.
  4. 검색 로봇을 위한 구조화 데이터(`Schema.org`) 및 이미지 전용 XML 사이트맵 규격 적용.

---

## 2. 테이블 스키마 최적화 (Database Migration)

### 2.1 `images` 테이블
* 파일 자체의 물리적 사양, WebP 경로, 검색엔진 색인용 메타데이터 보관.

    use Illuminate\Database\Migrations\Migration;
    use Illuminate\Database\Schema\Blueprint;
    use Illuminate\Support\Facades\Schema;

    return new class extends Migration
    {
        public function up(): void
        {
            Schema::create('images', function (Blueprint $table) {$table->id()->comment('이미지 고유 식별 번호');

                // 1. 소유 및 권한 (폴더 트리는 별도 피벗 테이블로 관리)
                $table->foreignId('shop_id')->nullable()->constrained('shops')->nullOnDelete()->comment('소유 뷰티샵 ID (null: 플랫폼 공용)');
                $table->foreignId('uploaded_by')->nullable()->constrained('users')->nullOnDelete()->comment('업로더 유저 ID');
                $table->string('visibility', 20)->default('public')->comment('공개 범위 (public, private)');

                // 2. 파일 시스템 정보 및 차세대 포맷 (Core Web Vitals 최적화)
                $table->string('disk', 50)->default('public')->comment('파일 저장소 드라이버');
                $table->string('path')->comment('원본 스토리지 상대 경로');
                $table->string('webp_path')->nullable()->comment('WebP 변환 파일 상대 경로 (구글 모바일 로딩 최적화)');
                $table->string('thumbnail_path')->nullable()->comment('목록 및 모달용 썸네일 상대 경로');
                $table->string('filename')->comment('디스크에 저장된 물리적 파일명');
                $table->string('original_name')->comment('사용자가 업로드한 원본 파일명');
                $table->string('mime_type', 50)->nullable()->comment('MIME 타입');
                $table->unsignedBigInteger('size')->comment('파일 용량 (Byte)');
                $table->unsignedInteger('width')->nullable()->comment('가로 해상도(px, 누적 레이아웃 이동 CLS 방지)');
                $table->unsignedInteger('height')->nullable()->comment('세로 해상도(px, 누적 레이아웃 이동 CLS 방지)');

                // 3. 무결성 및 중복 차단
                $table->string('hash', 64)->unique()->comment('SHA-256 고유 해시값 (Zero Duplication)');

                // 4. 수동 보호 플래그
                $table->boolean('is_temp')->default(false)->index()->comment('임시 업로드 여부');
                $table->boolean('is_protected')->default(false)->index()->comment('관리자 수동 삭제 방지 잠금(🔒)');

                // 5. 검색엔진(구글, 네이버) 노출 및 유입 극대화 메타데이터
                $table->string('seo_slug')->nullable()->index()->comment('검색 친화적 키워드 슬러그 URL');
                $table->string('title')->nullable()->comment('이미지 제목 (Google Image Title/Caption)');
                $table->string('alt_text')->nullable()->comment('대체 텍스트 (검색엔진 색인의 핵심 키워드)');
                $table->text('description')->nullable()->comment('상세 설명 및 본문 figcaption 노출용 캡션');
                $table->json('tags')->nullable()->comment('검색 키워드 태그 (JSON 배열)');
                $table->string('credit')->nullable()->default('GLOBEAU')->comment('저작권자/출처 표기 (Schema.org author/copyrightHolder)');

                // 6. 사용량 캐시
                $table->unsignedInteger('usage_count')->default(0)->index()->comment('연결된 도메인 엔티티(imageables) 수');

                $table->timestamps();
            });
        }

        public function down(): void
        {
            Schema::dropIfExists('images');
        }
    };

### 2.2 `imageables` 테이블 (기존 유지)
* 다형성(Polymorphic) 매핑을 통해 특정 이미지(`images.id`)가 뷰티샵, 프로그램, 제품의 어떤 슬롯(`type`: logo, banner, og_image, marketing, editor)에 매핑되는지 관리.
* 검색포털에서 이미지를 클릭했을 때 연결되는 본문 페이지 URL을 역추적(Canonical URL)하는 근간 역할 수행.

---

## 3. 포털 검색 상위 노출 5대 핵심 질문 및 동작 원리

### 3.1 WebP 파일은 업로드 시 자동으로 생성되는가?
* **동작 원리**: 라라벨 백엔드 컨트롤러(예: Intervention Image v3 라이브러리)에서 원본 파일 저장 직후 백그라운드 또는 인라인에서 WebP 포맷(압축 품질 약 80~85%)으로 동시 변환 후 동일 디렉터리에 저장.
* **효과**: 2~4MB 수준의 고화질 원본 사진이 150~300KB 수준으로 경량화되어, 구글 모바일 검색 랭킹의 핵심 지표인 LCP(Largest Contentful Paint) 속도를 비약적으로 단축.

### 3.2 `alt_text`와 `seo_slug`는 자동으로 만들어지는가?
* **동작 원리**:
  1. 업로드된 파일명(예: `수원_광교_헤어스파_두피케어_01.jpg`)에서 확장자를 분리.
  2. 밑줄(`_`) 및 하이픈(`-`)을 공백으로 치환해 **`alt_text` = "수원 광교 헤어스파 두피케어 01"**로 자동 초기화.
  3. `Str::slug()`를 적용해 **`seo_slug` = "suwon-gwanggyo-heospa-dupikeeo-01"**을 자동 채번.
* **효과**: 관리자가 메타데이터를 일일이 타이핑하지 않아도 업로드 즉시 완벽한 검색 친화적 키워드가 주입되며, 필요 시 사후 수정 가능.

### 3.3 모든 이미지를 스크립트로 처리해야 하는가?
* **동작 원리**: 사용자는 평소처럼 관리자 창에서 파일을 드래그 앤 드롭하기만 하면 됨. 서버 측 업로드 처리 스크립트(컨트롤러/Job)가 단일 파이프라인에서 다음 단계를 일괄 자동 처리:
  1. 원본 파일 저장 및 SHA-256 해시 검증
  2. 해상도(`width`, `height`) 자동 추출
  3. WebP 변환 및 저장
  4. 파일명 기반 `alt_text`, `title`, `seo_slug`, `tags` 자동 파싱 및 DB 기록

### 3.4 `tags` 필드는 내부 검색용으로만 쓰이는가?
* **동작 원리**:
  1. **내부**: 관리자 모달 라이브러리 내 키워드 검색용 인덱스.
  2. **외부**: 본문 렌더링 시 구글/네이버가 읽어가는 구조화 데이터(`Schema.org ImageObject`)의 `keywords` 배열 필드로 직접 전달되어 포털 알고리즘의 매칭 키워드로 활용됨.

### 3.5 저작권자 표기(`credit`)가 포털 검색과 관련이 있는가?
* **관련성**: 매우 높음 (구글 공식 검색 규격).
* **효과**:
  1. **구글 라이선스 배지(Licensable Badge)**: 저작권자(`copyrightHolder`)와 출처(`creditText`)가 명시된 이미지는 구글 이미지 검색창에 공식 출처 배지가 부착되어 신뢰도와 클릭률이 상승함.
  2. **오리지널 저작권 보호**: 타 블로그나 사이트에서 이미지를 불펌해가더라도 검색 로봇이 GLOBEAU를 원본 소유자로 인식하여 원본 사이트에 우선 순위를 부여함.

---

## 4. 백엔드 업로드 자동화 컨트롤러 구현 예시

    namespace App\Http\Controllers\Admin;

    use App\Http\Controllers\Controller;
    use Illuminate\Http\Request;
    use App\Models\Image as ImageModel;
    use Intervention\Image\Laravel\Facades\Image;
    use Illuminate\Support\Str;
    use Illuminate\Support\Facades\Storage;

    class ImageUploadController extends Controller
    {
        public function upload(Request $request)
        {
            $request->validate([
                'image' => 'required|image|max:20480', // 최대 20MB
            ]);

            $file =$request->file('image');
            $originalFullName =$file->getClientOriginalName();
            $rawName = pathinfo($originalFullName, PATHINFO_FILENAME);

            // 1. alt_text 및 title 정제 (특수문자 제거 후 공백 정리)
            $cleanText = trim(preg_replace('/[_\-]+/', ' ',$rawName));
            $altText =$cleanText ?: 'GLOBEAU 뷰티 시술 이미지';

            // 2. 검색 친화적 SEO 슬러그 자동 생성
            $seoSlug = Str::slug($cleanText) ?: 'globeau-' . uniqid();

            // 3. 해시 계산 (중복 차단) 및 원본 저장
            $hash = hash_file('sha256',$file->getRealPath());
            $datePath = date('Y/m');$originalPath = $file->store("images/{$datePath}", 'public');

            // 4. 해상도 측정 및 WebP 자동 변환
            $imgInstance = Image::read($file);
            $width =$imgInstance->width();
            $height =$imgInstance->height();

            $webpFilename = "images/{$datePath}/" . pathinfo($originalPath, PATHINFO_FILENAME) . '.webp';
            $webpFullPath = storage_path("app/public/{$webpFilename}");

            // WebP 포맷(품질 82%) 압축 생성
            $imgInstance->toWebp(82)->save($webpFullPath);

            // 5. 태그 자동 추출 (단어 단위 분리)
            $tags = array_values(array_filter(explode(' ',$cleanText)));

            // 6. DB 레코드 생성
            $image = ImageModel::create([
                'shop_id'       => $request->input('shop_id'),
                'uploaded_by'   => auth()->id(),
                'disk'          => 'public',
                'path'          => $originalPath,
                'webp_path'     => $webpFilename,
                'filename'      => basename($originalPath),
                'original_name' => $originalFullName,
                'mime_type'     => $file->getMimeType(),
                'size'          => $file->getSize(),
                'width'         => $width,
                'height'        => $height,
                'hash'          => $hash,
                'seo_slug'      => $seoSlug,
                'title'         => $cleanText,
                'alt_text'      => $altText,
                'description'   => $cleanText,
                'tags'          => $tags,
                'credit'        => 'GLOBEAU',
            ]);

            return response()->json([
                'success' => true,
                'image'   => $image,
            ]);
        }
    }

---

## 5. 프론트엔드 본문 페이지 검색 최적화 적용 규격

### 5.1 본문 시맨틱 `<picture>` 마크업 (WebP 우선 + 원본 Fallback)

    <figure class="figure w-100 my-3">
        <picture>
            @if($image->webp_path)
                <source srcset="{{ Storage::disk($image->disk)->url($image->webp_path) }}" type="image/webp">
            @endif
            <img src="{{ Storage::disk($image->disk)->url($image->path) }}" 
                 alt="{{ $image->alt_text ?: $image->title }}" 
                 title="{{ $image->title }}"
                 width="{{ $image->width }}" 
                 height="{{ $image->height }}" 
                 loading="lazy" 
                 decoding="async" 
                 class="img-fluid rounded shadow-sm">
        </picture>
        @if($image->description)
            <figcaption class="figure-caption mt-2 text-center text-secondary small">
                {{ $image->description }}
            </figcaption>
        @endif
    </figure>

### 5.2 구글/네이버 수집용 구조화 데이터 (Schema.org JSON-LD)

    <script type="application/ld+json">
    {
      "@context": "https://schema.org/",
      "@type": "ImageObject",
      "contentUrl": "{{ Storage::disk($image->disk)->url($image->webp_path ?: $image->path) }}",
      "name": "{{ $image->title }}",
      "description": "{{ $image->description }}",
      "caption": "{{ $image->alt_text }}",
      "acquireLicensePage": "{{ url()->current() }}",
      "creditText": "{{ $image->credit ?: 'GLOBEAU' }}",
      "copyrightHolder": {
        "@type": "Organization",
        "name": "GLOBEAU"
      },
      "keywords": @json($image->tags ?? []),
      "width": "{{ $image->width }} px",
      "height": "{{ $image->height }} px",
      "uploadDate": "{{ $image->created_at->toIso8601String() }}"
    }
    </script>

### 5.3 이미지 전용 XML 사이트맵 규격 (`/sitemap-images.xml`)

    <?xml version="1.0" encoding="UTF-8"?>
    <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
            xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">
        <url>
            <loc>https://globeau.com/shops/gwanggyo-beauty/programs/head-spa</loc>
            <lastmod>2026-10-01</lastmod>
            <image:image>
                <image:loc>https://globeau.com/storage/images/2026/10/head-spa-care.webp</image:loc>
                <image:title>수원 광교 두피 스케일링 전후 케어</image:title>
                <image:caption>GLOBEAU 공식 인증 전문 트리콜로지스트 시술 케어</image:caption>
            </image:image>
        </url>
    </urlset>