ใน NextJS หากเรามีการใช้งานรูปภาพ NextJS จะแนะนำให้ เราจะใช้ next/image เพื่อช่วยให้เราสามารถจัดการและแสดงรูปภาพบนเว็บไซต์ได้อย่างง่ายดายและมีประสิทธิภาพ

เช่น:

  • Responsive Images: ปรับขนาดรูปภาพอัตโนมัติให้เหมาะสมกับอุปกรณ์ที่ใช้งานโดยไม่ต้องเขียนโค้ดเอง
  • Lazy Loading: โหลดรูปภาพเฉพาะเมื่อถูกเลื่อนไปถึงเพื่อปรับปรุงประสิทธิภาพของหน้าเว็บ
  • Automatic Optimization: Optimize images for web formats like WebP for enhanced performance
  • SEO Friendly: สร้าง markup ที่เหมาะสมสำหรับเครื่องมือค้นหา

แต่เมื่อเราต้องการทดสอบ component ที่มีการใช้งาน next/image ปรากฏว่ามันไม่แสดงผล ดังภาพ

import Image from 'next/image'
import React from 'react'

type Props = {}

const CustomImage = (props: Props) => {
  return (
    <div>
      <h1>Custom Image</h1>
      {/* public/example.webp */}
      <Image src='/example.webp' alt='example image' width={300} height={165} />
    </div>
  )
}

export default CustomImage

custom-image.tsx

preview in cypress

ถ้าเราลอง inspect ดูจะพบว่า มันไปเรียกไฟล์ ที่ /_next/image?url=%2Fexample.webp&w=640&q=75 ซึ่งมันไม่ใช่ที่อยู่ของไฟล์ ที่เรากำหนดไว้

inspect

ที่เป็นแบบนี้ เป็นเพราะว่า เวลา run NextJS (next dev / next build) NextJS จะทำการจัดการไฟล์รูปภาพแล้วนำไปเก็บไว้ที่โฟลเดอร์ next/static/media/* ซึ่งเมื่อเราทำสอบด้วย cypress ตัว cypress จะไปหาไฟล์นั้นไม่เจอ


วิธีจัดการกับไฟล์ภาพ

การจัดการกับการเข้าถึงภาพเหล่านั้น เราจะไปเข้าไปหาไฟล์ภาพใน _next โดยตรง เพราะชื่อภาพจะเปลี่ยนแปลงทุกครั้งที่มีการ run หรือ build ใหม่ ทำให้ยากต่อการจัดการ และบางถาพเราอาจจะไม่รู้ว่า ภาพที่เราใช้มันชื่อว่าอะไร เพราะมันจะเพิ่มชื่อให้ภาพที่ผ่านการจัดการแล้วเราด้วย

ดังนั้นสิ่งที่เราจะทำคือ เปลี่ยนที่อยู่ของภาพให้มัน link ไปยังไฟล์ภาพต้นฉบับ ซึ่งวิธีนี้เราจะต้องทำตัว mock next/image ขึ้นมา โดยมีวิธีการดังนี้

ติดตั้ง webpack

npm install -D webpack
หรือ
yarn add -D webpack

install webpack

กำหนดที่อยู่ของไฟล์ Mock Image

จากนั้นให้เราไปกำหนดค่าในไฟล์ cypress.config.ts

import { defineConfig } from "cypress";
import path from "path";
import { NormalModuleReplacementPlugin } from "webpack";

export default defineConfig({
  component: {
    devServer: {
      framework: "next",
      bundler: "webpack",
      // Add webpackConfig
      webpackConfig: {
        plugins: [
          new NormalModuleReplacementPlugin(
            /next\/image/,
            require.resolve(
              path.join(__dirname, "cypress", "mocks", "next", "image")
            )
          ),
        ],
      },
    },
  },
});

cypress.config.ts

new NormalModuleReplacementPlugin เป็นการ replece ตัว module ที่เราต้องการ โดยจะกำหนดให้ไปเรียกใช้งานไฟล์ image ที่อยู่ที่ cypress/mocks/next/image แทน

สร้างไฟล์ Mock Image

เมื่อกำหนด config เรียบร้อยแล้ว ให้เราไปเพิ่มไฟล์ image.ts ในโฟล์เดอร์ cypress/mocks/next/image.ts

// cypress/mocks/next/image.tsx
import type { ImageProps } from 'next/image'

/**
 * Converts the next/image static image URL to a regular path.
 *
 * Example:
 *
 * /_next/static/media/404.ea2b1f50.png -> /assets/images/404.png
 */
const convertURL = (url: string) => {
  const newURL = url
    .replace(/\/_next\/static\/media\//, '/public/') // Use actual images location
    .replace(/(?<=\.)(.+)(?=png|jp?eg|tiff?|png|webp|bmp|gif|svg)/, '')
  return newURL
}

const Image = (props: ImageProps) => {
  // Regular path to image resource
  if (typeof props.src === 'string') {
    return (
      // eslint-disable-next-line @next/next/no-img-element
      <img
        src={props.src}
        alt={props.alt}
        width={props.width}
        height={props.height}
        style={props.style}
      />
    )
  }

  let src: string
  // StaticImageData - an import of image resource
  if ('src' in props.src) {
    src = props.src.src
  } else {
    // StaticRequire
    src = props.src.default.src
  }

  // eslint-disable-next-line @next/next/no-img-element
  return <img src={convertURL(src)} alt={props.alt} style={props.style} />
}

export default Image

image.ts

เพียงเท่านี้ เราก็สามารถ link ภาพไปยังไฟล์ภาพต้นฉบับได้แล้ว