Cách thiết lập tối ưu hóa hình ảnh Vercel trong TanStack Start
Mặc dù Vercel chỉ cung cấp các gói chứa component <Image />
cho Next.js, Nuxt, và Astro, nhưng bạn vẫn có thể kích hoạt Image Optimization (Tối ưu hóa hình ảnh) trên bất kỳ framework nào bằng cách sử dụng Vercel Build Output API.
Bước 1: Chỉnh sửa cấu hình Vercel
Điều đầu tiên bạn cần làm là thông báo cho Vercel rằng bạn muốn sử dụng API tối ưu hóa hình ảnh. Để làm được điều này, ta cần chỉnh sửa bundler để thêm các thông số bổ sung vào file ./vercel/output/config.json
sau khi build.
TanStack Start sử dụng Nitro bên trong, và Nitro cung cấp cách đơn giản để thực hiện điều này. Bạn chỉ cần tạo một file tên là nitro.config.ts với nội dung sau:
export default defineNitroConfig({
vercel: {
config: {
images: {
domains: ["yourdomain.example.com"], // Change to your domain
sizes: [
16, 32, 48, 64, 96, 128, 256, 384, 640, 750, 828, 1080, 1200, 1920,
2048, 3840,
],
minimumCacheTTL: 60,
formats: ["image/webp"],
// Next.js uses the same values for sizes, cacheTTL, and formats
},
},
},
});
Bước 2: Cài đặt chiến lược tối ưu hóa
Sau khi bạn thiết lập cấu hình, khi triển khai ứng dụng lên Vercel, endpoint /_vercel/image
sẽ được proxy tới dịch vụ tối ưu hóa hình ảnh của Vercel.
Bây giờ, bạn chỉ cần triển khai component để tận dụng tính năng này. Gói next/image
có hàng ngàn dòng mã nguồn để hỗ trợ nhiều dịch vụ tối ưu khác nhau và nhiều chiến lược khác nhau — nhưng đa số các ứng dụng không cần sự phức tạp đó.
Ta có thể đơn giản hóa logic bằng cách sử dụng một hook như sau:
import { useMemo } from "react";
const imageWidths = [
16, 32, 48, 64, 96, 128, 256, 384, 640, 750, 828, 1080, 1200, 1920, 2048,
3840,
] as const;
type ImgWidth = (typeof imageWidths)[number];
const getVercelOptimizedUrl = (url: string, width: ImgWidth) => {
const searchParams = new URLSearchParams();
searchParams.append("url", url);
searchParams.append("w", width.toString());
searchParams.append("q", "75");
return `/_vercel/image?${searchParams.toString()}`;
};
export const useVercelOptimizedImageProps = (
src: string,
width: number,
height: number,
) =>
useMemo(() => {
if (
!import.meta.env.VITE_VERCEL_ENV ||
import.meta.env.VITE_VERCEL_ENV === "development"
)
return { src, width, height };
const widths = [
...new Set(
[width, width * 2].map(
(w) => imageWidths.find((p) => p >= w) || imageWidths.at(-1)!,
),
),
] as [ImgWidth, ...Array<ImgWidth>];
return {
srcSet: widths
.map((w, i) => `${getVercelOptimizedUrl(src, w)} ${i + 1}x`)
.join(", "),
width: widths[0],
height: Math.round((widths[0] * height) / width),
};
}, [src, width, height]);
interface ImageProps extends React.ImgHTMLAttributes<HTMLImageElement> {
src: string;
width: number;
height: number;
alt: string;
}
export const Image = ({
src,
width,
height,
loading = "lazy",
...props
}: ImageProps) => {
const imgProps = useVercelOptimizedImageProps(src, width, height);
return <img loading={loading} {...imgProps} {...props} />;
};
Component này sẽ hoạt động giống như component next/image
trong 99% các trường hợp.
Hy vọng bài viết này sẽ giúp ích cho các bạn!
All rights reserved