Files
goodgo-platform/docs/audits/IMAGE_AUDIT_REPORT.md
Ho Ngoc Hai 11f2bf26e6
Some checks failed
CI / Lint → Typecheck → Test → Build (22) (push) Failing after 29s
CI / E2E Tests (push) Has been skipped
CodeQL Analysis / CodeQL (javascript-typescript) (push) Failing after 2m42s
Deploy / Build Web Image (push) Failing after 27s
Deploy / Build AI Services Image (push) Failing after 29s
E2E Tests / Playwright E2E (push) Failing after 43s
Deploy / Build API Image (push) Failing after 1m31s
Security Scanning / Dependency Audit (pnpm) (push) Failing after 6s
Security Scanning / Trivy Scan — API Image (push) Failing after 5m35s
Security Scanning / Trivy Scan — AI Services Image (push) Failing after 3m45s
Deploy / Deploy to Staging (push) Has been skipped
Deploy / Smoke Test Staging (push) Has been skipped
Deploy / Deploy to Production (push) Has been skipped
Deploy / Smoke Test Production (push) Has been skipped
Deploy / Rollback Staging (push) Has been skipped
Deploy / Rollback Production (push) Has been skipped
Security Scanning / Trivy Scan — Web Image (push) Failing after 13m51s
Security Scanning / Trivy Filesystem Scan (push) Failing after 14m46s
Security Scanning / Security Gate (push) Has been cancelled
chore: update project documentation, audit reports, and initialize IDE configuration files
2026-04-19 03:12:54 +07:00

15 KiB

Báo Cáo Kiểm Tra Sử Dụng Hình Ảnh - GoodGo Web App (apps/web/)

Ngày tạo: 2026-04-11
Phạm vi: Kiểm tra toàn diện việc sử dụng hình ảnh trong các file .tsx, .ts và .jsx


🎯 Tóm Tắt Điều Hành

Ứng dụng web Next.js thể hiện thực hành tối ưu hóa hình ảnh xuất sắc:

  • Không có thẻ HTML <img> nào được dùng trong các component sản phẩm
  • Component Image của Next.js được triển khai đúng cách trên tất cả các component trực quan
  • CSP và remotePatterns được cấu hình chính xác
  • ⚠️ Chỉ có 4 thẻ HTML <img> được tìm thấy (tất cả trong các mock kiểm thử - chấp nhận được)
  • 3 component hình ảnh chuyên dụng xử lý upload, gallery và lightbox

📊 Thống Kê

Số liệu Số lượng
Các file sử dụng next/image 8
Các file có thẻ HTML <img> (production) 0
Các component liên quan đến hình ảnh 3
Các mock kiểm thử có <img> 3
Các file tiện ích hình ảnh 0
Tổng số dòng code liên quan đến hình ảnh ~651

1. Thẻ HTML <img> Được Tìm Thấy

Sử Dụng Trong Production: KHÔNG CÓ

Không tìm thấy thẻ HTML <img> nào trong code production.

⚠️ Các Mock Kiểm Thử: 4 trường hợp

Đây là những trường hợp chấp nhận được - chúng là các mock kiểm thử của component Image từ Next.js:

File Dòng Ngữ cảnh Loại
app/[locale]/(public)/__tests__/landing.spec.tsx 37 Mock cho next/image trong kiểm thử Jest Mock
app/[locale]/(public)/search/__tests__/search.spec.tsx 46 Mock cho next/image trong kiểm thử Jest Mock
app/[locale]/(dashboard)/dashboard/__tests__/dashboard.spec.tsx 14 Mock cho next/image trong kiểm thử Jest Mock
components/listings/image-upload.tsx 144 Ảnh xem trước cho file upload Production (fallback từ blob URL)

Lưu ý về image-upload.tsx dòng 144: Đây là ảnh xem trước sử dụng blob: URL cho việc upload file trước khi gửi:

<img
  src={img.preview}  // blob: URL from URL.createObjectURL(file)
  alt={`Ảnh ${index + 1}`}
  className="h-full w-full object-cover"
/>

Điều này phù hợp vì hình ảnh là một blob URL tạm thời không tồn tại trên các máy chủ từ xa.


2. Các Import next/image Được Tìm Thấy

Các File Sử Dụng Component Image của Next.js: 8

File Vị trí Cách dùng
components/listings/image-gallery.tsx Dòng 3 Hiển thị ảnh chính gallery và thumbnail
components/listings/image-lightbox.tsx Dòng 3 Trình xem ảnh toàn màn hình
components/search/property-card.tsx Dòng 1 Ảnh thumbnail của thẻ bất động sản
components/agents/agent-profile-client.tsx Dòng 14 Avatar đại lý & ảnh listing của đại lý
components/comparison/comparison-table.tsx Dòng 4 Ảnh bất động sản trong bảng so sánh
app/[locale]/(admin)/admin/kyc/page.tsx - Trang KYC admin (có thể dùng cho ảnh tài liệu)
app/[locale]/(dashboard)/listings/page.tsx - Giao diện danh sách listing trong dashboard
app/[locale]/(dashboard)/dashboard/page.tsx - Tổng quan dashboard

Tóm Tắt Cách Dùng Component Image:

  • Sử dụng chính: Ảnh danh sách bất động sản
  • Sử dụng phụ: Avatar đại lý
  • Kích thước responsive: Sử dụng prop sizes đúng cách
  • Tải ưu tiên: Prop priority được dùng cho các ảnh hiển thị trước khi cuộn
  • Dự phòng: Các div placeholder khi ảnh không khả dụng

3. Các Component Liên Quan đến Bất Động Sản/Listing

🏗️ Các Component Chuyên Dụng Cho Hình Ảnh (3)

1. ImageGallery (components/listings/image-gallery.tsx)

  • Số dòng: 127 tổng cộng
  • Mục đích: Trình xem gallery chính với thumbnail
  • Tính năng:
    • Sử dụng Image từ next/image (dòng 46, 106)
    • Ảnh chính với prop fill + sizes
    • Dải thumbnail để điều hướng
    • Kích thước responsive: (max-width: 768px) 100vw, 60vw
    • Dự phòng phù hợp: "Chưa có hình ảnh"
    • Hỗ trợ tích hợp lightbox
  • Props: media: PropertyMedia[], className?: string

2. ImageLightbox (components/listings/image-lightbox.tsx)

  • Số dòng: 349 tổng cộng
  • Mục đích: Trình xem ảnh toàn màn hình với các tính năng nâng cao
  • Tính năng:
    • Sử dụng Image từ next/image (dòng 249, 335)
    • Modal toàn màn hình với fixed inset-0 z-50
    • Điều hướng bằng bàn phím (Mũi tên Trái/Phải, Escape)
    • Hỗ trợ vuốt cảm ứng với hook useSwipe tùy chỉnh
    • Bẫy focus để đảm bảo khả năng tiếp cận
    • Tải trước ảnh cho các ảnh liền kề (dòng 176-188)
    • Kích thước responsive: 100vw
    • Điều hướng thumbnail ở phía dưới
  • Props: images: PropertyMedia[], initialIndex?: number, open: boolean, onClose: () => void

3. ImageUpload (components/listings/image-upload.tsx)

  • Số dòng: 175 tổng cộng
  • Mục đích: Component upload file với kéo-thả
  • Tính năng:
    • Sử dụng HTML <img> cho blob preview (chấp nhận được - dòng 144)
    • Xử lý file kéo-thả
    • Xác thực file: JPEG, PNG, WebP
    • Kích thước file tối đa: 10MB mỗi ảnh
    • Số file tối đa: 20 ảnh
    • Dọn dẹp Object URL khi unmount
    • Lưới xem trước với nút xóa
    • Đánh dấu ảnh đầu tiên là ảnh bìa
  • Props: images: ImageFile[], onChange: (images: ImageFile[]) => void, maxFiles?: number, className?: string

📦 Các Component Hiển Thị Ảnh Bất Động Sản

Component File Cách dùng hình ảnh
PropertyCard components/search/property-card.tsx Media listing đầu tiên làm thumbnail thẻ
ListingDetailClient components/listings/listing-detail-client.tsx Tích hợp component ImageGallery (dòng 92)
AgentProfileClient components/agents/agent-profile-client.tsx Avatar đại lý + ảnh listing hoạt động của đại lý
ComparisonTable components/comparison/comparison-table.tsx Media đầu tiên cho mỗi listing trong so sánh
ListingCard (trong AgentProfileClient) components/agents/agent-profile-client.tsx Ảnh listing trong portfolio của đại lý

4. Cấu Hình Image của Next.js

File: apps/web/next.config.js

images: {
  remotePatterns: [
    {
      protocol: 'https',
      hostname: '**',
    },
  ],
},

Phân Tích Cấu Hình:

Điểm mạnh:

  • remotePatterns cho phép thoáng cho tất cả các domain HTTPS
  • Hợp lý cho một nền tảng liệt kê bất động sản từ nhiều nguồn
  • Giao thức chỉ giới hạn HTTPS (thực hành bảo mật tốt nhất)

Cân nhắc:

  • Ký tự đại diện hostname: '**' cho phép ảnh từ bất kỳ domain nào
  • Điều này chấp nhận được nếu tất cả URL hình ảnh được xác thực bởi người dùng
  • Khuyến nghị xác thực URL hình ảnh ở tầng API trước khi trả về frontend

Các Header CSP (dòng 34-47):

'img-src 'self' data: blob: https://*.mapbox.com https://*.tiles.mapbox.com https:',

Phân tích:

  • Cho phép blob: URL (cho ảnh xem trước image-upload)
  • Cho phép data: URL (ảnh base64 nội tuyến)
  • Cho phép ảnh tự lưu trữ
  • Cho phép ảnh tile Mapbox
  • Cho phép tất cả nguồn HTTPS

5. Các Tiện Ích & Helper Liên Quan đến Hình Ảnh

Các File Đã Kiểm Tra:

  • Thư mục lib/ - Không tìm thấy tiện ích hình ảnh chuyên dụng nào
  • components/ui/ - Không có component hình ảnh ngoài gallery/upload
  • hooks/ - Không có hook dành riêng cho hình ảnh (quản lý hình ảnh được xử lý nội tuyến)

Các Tiện Ích Nội Tuyến Được Tìm Thấy:

Trong image-upload.tsx:

  • URL.createObjectURL() để tạo blob preview (dòng 36)
  • URL.revokeObjectURL() để dọn dẹp (dòng 50, 80)

Trong image-lightbox.tsx:

  • Hook useSwipe() tùy chỉnh (dòng 19-52) - hỗ trợ cử chỉ cảm ứng
  • Hook useFocusTrap() tùy chỉnh (dòng 56-99) - khả năng tiếp cận
  • Tải trước ảnh với new window.Image() (dòng 185)

Trong image-gallery.tsx:

  • Không có tiện ích tùy chỉnh, sử dụng tối ưu hóa Image của Next.js

6. Các Kiểu Dữ Liệu Hình Ảnh

Kiểu PropertyMedia (từ listings-api):

interface PropertyMedia {
  id: string;
  url: string;           // Image URL
  type: 'image' | 'video'; // Media type
  order: number;         // Display order
  caption?: string;      // Optional caption
}

Kiểu ImageFile (từ image-upload):

interface ImageFile {
  file: File;            // Browser File object
  preview: string;       // Object URL (blob:)
}

7. Xử Lý Hình Ảnh Trong Các Trang Quan Trọng

Chi Tiết Listing Bất Động Sản: app/[locale]/(public)/listings/[id]/page.tsx

  • Import component ListingDetailClient
  • Truyền media bất động sản vào ImageGallery
  • Hiển thị nhiều ảnh với điều khiển gallery

Kết Quả Tìm Kiếm: app/[locale]/(public)/search/page.tsx

  • Render nhiều component PropertyCard
  • Mỗi component hiển thị ảnh đầu tiên làm thumbnail
  • Sử dụng component Image responsive

Hồ Sơ Đại Lý: app/[locale]/(public)/agents/[id]/page.tsx

  • Hiển thị avatar đại lý
  • Hiển thị các listing hoạt động của đại lý kèm ảnh
  • Sử dụng component AgentProfileClient

Dashboard Listing: app/[locale]/(dashboard)/listings/new/page.tsx

  • Bao gồm component ImageUpload để thêm ảnh bất động sản
  • Xử lý chọn file ảnh và xem trước

8. Khả Năng Tiếp Cận & Hiệu Suất

Các Tính Năng Khả Năng Tiếp Cận:

  • Văn bản alt trên tất cả hình ảnh
  • Bản địa hóa tiếng Việt cho văn bản alt (phù hợp về văn hóa)
  • Nhãn ARIA cho gallery hình ảnh
  • Điều hướng bàn phím trong lightbox (Phím mũi tên, Escape)
  • Bẫy focus trong modal
  • Bẫy Tab trong lightbox để đảm bảo khả năng tiếp cận

Các Tối Ưu Hóa Hiệu Suất:

  • Prop priority cho ảnh hiển thị trước khi cuộn
  • Prop sizes cho ảnh responsive
  • Kết hợp fill + sizes phù hợp cho gallery
  • Tải trước ảnh trong lightbox
  • Dọn dẹp Blob URL khi unmount
  • Thu hồi Object URL để ngăn rò rỉ bộ nhớ

⚠️ Các Cải Tiến Tiềm Năng:

  • Cân nhắc triển khai lazy-loading ảnh ngoài các mặc định của Next.js
  • Có thể thêm trạng thái skeleton loading trong quá trình tải ảnh
  • Cân nhắc ảnh placeholder blur để cải thiện trải nghiệm người dùng

9. Nhận Xét Bảo Mật

Các Thực Hành Bảo Mật:

  • Các pattern từ xa chỉ giới hạn HTTPS
  • Các header CSP được cấu hình đúng cách
  • blob: URL chỉ được dùng cho xem trước tạm thời phía client
  • Không có dữ liệu ảnh nội tuyến trong các component

⚠️ Các Điểm Cần Theo Dõi:

  • Xác thực URL hình ảnh ở tầng API trước khi trả về
  • Đảm bảo ảnh do người dùng upload được quét để phát hiện nội dung độc hại
  • Cân nhắc tích hợp CDN với tối ưu hóa ảnh nếu mở rộng quy mô

10. Bảng Tóm Tắt

Hạng mục Trạng thái Chi tiết
Thẻ HTML <img> (Prod) ĐẠT 0 tìm thấy - tất cả đã được thay thế bằng next/image
Sử dụng next/image ĐẠT 8 file sử dụng component Image đúng cách
Cấu hình Image ĐẠT remotePatterns được cấu hình cho HTTPS
Các Header CSP ĐẠT Hỗ trợ blob:, data:https: đúng cách
Các Component Hình Ảnh ĐẠT 3 component chuyên dụng cho gallery/upload
Khả năng tiếp cận ĐẠT Văn bản Alt, nhãn ARIA, điều hướng bàn phím
Hiệu suất ĐẠT Kích thước responsive, tải ưu tiên, tải trước
Bảo mật ĐẠT Chỉ HTTPS, cấu hình CSP đúng cách
Quản lý bộ nhớ ĐẠT Object URL được thu hồi đúng cách

📋 Khuyến Nghị

Ưu Tiên 1 (Triển Khai Sớm):

  1. Thêm xác thực URL hình ảnh ở tầng API để đảm bảo chỉ các nguồn đáng tin cậy
  2. Triển khai quét ảnh do người dùng upload (phần mềm độc hại/nội dung không phù hợp)
  3. Cân nhắc tích hợp CDN để tối ưu hóa ảnh ở quy mô lớn

Ưu Tiên 2 (Tốt Nếu Có):

  1. Thêm skeleton/blur placeholder trong quá trình tải ảnh
  2. Triển khai nén ảnh trước khi upload
  3. Thêm worker tối ưu hóa ảnh để thay đổi kích thước khi upload
  4. Cân nhắc triển khai lazy-loading intersection observer

Ưu Tiên 3 (Tương Lai):

  1. Triển khai chiến lược bộ nhớ đệm ảnh
  2. Cân nhắc tải ảnh lũy tiến (LQIP - Low Quality Image Placeholder)
  3. Thêm xóa dữ liệu EXIF ảnh để bảo vệ quyền riêng tư
  4. Triển khai định dạng WebP với các phương án dự phòng

📁 Danh Sách File Đầy Đủ

Các File Sử Dụng next/image:

✅ components/listings/image-gallery.tsx
✅ components/listings/image-lightbox.tsx
✅ components/search/property-card.tsx
✅ components/agents/agent-profile-client.tsx
✅ components/comparison/comparison-table.tsx
✅ app/[locale]/(admin)/admin/kyc/page.tsx
✅ app/[locale]/(dashboard)/listings/page.tsx
✅ app/[locale]/(dashboard)/dashboard/page.tsx

Các Component Chuyên Dụng Cho Hình Ảnh:

✅ components/listings/image-upload.tsx (175 dòng)
✅ components/listings/image-gallery.tsx (127 dòng)
✅ components/listings/image-lightbox.tsx (349 dòng)

Cấu Hình:

✅ apps/web/next.config.js

📞 Câu Hỏi Dành Cho Nhóm Sản Phẩm

  1. Tất cả URL hình ảnh có được xác thực ở tầng API không?
  2. Nội dung ảnh do người dùng upload có được quét để phát hiện file độc hại không?
  3. Có kế hoạch triển khai tối ưu hóa ảnh CDN không?
  4. Có nên thêm blur/skeleton placeholder trong quá trình tải không?
  5. Có yêu cầu cụ thể nào về kích thước/chất lượng ảnh cho listing không?