使用 Next.js 來生成 Blog 的一個主要好處是可以使用 next/image component 來做圖片最佳化。
在官網的範例看起來,似乎是一個非常容易使用的 component
import Image from 'next/image'
import profilePic from '../public/me.png'
// in your component...
return <Image src={profilePic} alt="Picture of the author" />
然而這裡如此簡單的使用是因為 profilePic
是被 static import 的。這個情況下 Next.js 可以自動幫你判斷圖片大小。
但當我們的 Blog 資料來源是 markdown 檔案時,圖片的位置是被寫在 markdown 中的,自然無法像範例這樣 static import。
當非 static import 的時候,Next.js 會要求我們提供圖片的 width
與 height
值。
如果沒有提供的話,就會看到像這樣的錯誤。
Error: Image with src "/images/xxx.png" must use "width" and "height"
properties or "layout='fill'" property.
告訴我們必須使用 layout="fill"
或是提供圖片的長寬值。
由於 layout="fill"
並不符合我們的需求,所以需要想辦法提供圖片長寬值。
我的作法是在 getStaticProps
時,同時去找出所使用的圖片的大小,建表給 <Image>
component 使用。
部分程式碼片段:
import { promises as fs, existsSync, createReadStream } from 'fs'
import { join } from 'path'
import probe from 'probe-image-size';
export async function getImageSizeMap(slug: string) {
const postImagesDir = join(IMAGES_DIR, slug)
const imageFilenames = await fs.readdir(postImagesDir)
const dict = {}
const promises = imageFilenames.map(async (filename) => {
const path = join(postImagesDir, filename)
let {width, height} = await probe(createReadStream(path));
dict[`${slug}/${filename}`] = {width, height}
})
await Promise.all(promises)
return dict
}
export const getStaticProps: GetStaticProps = async ({ params }) => {
const { slug } = params
const [post, sizes] = await Promise.all([
getPostBySlug(slug, [
'title',
'date',
'slug',
'content',
]),
getImageSizeMap(slug)
])
return {
props: { post, sizes },
}
}
如此拿到各個圖片大小之後,就可以將長寬塞給 <Image>
而解決上述的問題。另外有需要的話也可以自己按比例調整圖片大小的值。
若使用 react-markdown 可以參考如下做法:
export default function Post({ post, sizes }: Props) {
function CustomImage({ alt, src }) {
const { width, height } = sizes[src]
return (
<Image
src={src} alt={alt}
width={width} height={height}
/>
)
}
return (
<AppLayout>
// ...
<ReactMarkdown components={{ img: CustomImage }}>
{post.content}
</ReactMarkdown>
</AppLayout>
)
}