Next.js 13组件中无需useEffect实现服务端图片模糊占位符
解决方案:在服务端生成模糊占位符(无需useEffect)
针对Next.js 13的场景,推荐两种无需客户端useEffect、完全在服务端处理的方案,既保留SSR优势,又避免plaiceholder增加客户端包体积。
方案一:将StrapiImage改为Async Server Component(App Router 默认支持)
Next.js 13 App Router下的组件默认是Server Component,支持异步写法,可直接在组件内完成模糊占位符的生成,所有逻辑在服务端执行,不会打包到客户端。
import Image from "next/image"; import { getStrapiMedia } from "../../lib/media"; import { getPlaiceholder } from "plaiceholder"; // 注意:不要加 'use client',保持为Server Component export default async function StrapiImage({ image, ...props }) { const imageUrl = getStrapiMedia(image); // 服务端请求图片二进制数据 const res = await fetch(imageUrl); const buffer = await res.arrayBuffer(); // 生成模糊占位符的base64字符串 const { base64 } = await getPlaiceholder(Buffer.from(buffer)); return ( <Image src={imageUrl} width={image.attributes.width} height={image.attributes.height} alt={image.attributes.alternativeText || ""} placeholder="blur" blurDataURL={base64} {...props} /> ); }
注意事项
- 如果Strapi图片需要权限验证,需在
fetch中添加对应凭证(如credentials: 'include'或自定义请求头) - 该组件完全在服务端渲染,
plaiceholder的代码不会进入客户端包
方案二:在数据获取阶段预生成blurDataURL(适配App/Page Router)
如果你的StrapiImage需要作为客户端组件(比如要添加交互逻辑),可以在页面的异步数据获取环节提前生成所有图片的模糊占位符,再传递给组件使用。
1. 页面端预处理数据(Server Component)
// app/posts/page.js(App Router示例) import StrapiImage from "@/components/StrapiImage"; import { getStrapiMedia } from "@/lib/media"; import { getPlaiceholder } from "plaiceholder"; export default async function PostsPage() { // 从Strapi获取原始数据 const strapiResponse = await fetch("https://your-strapi-api/posts", { cache: 'force-cache' }); const postsData = await strapiResponse.json(); // 批量生成图片的blurDataURL const processedPosts = await Promise.all( postsData.data.map(async (post) => { const image = post.attributes.image; const imageUrl = getStrapiMedia(image); const res = await fetch(imageUrl); const buffer = await res.arrayBuffer(); const { base64 } = await getPlaiceholder(Buffer.from(buffer)); // 把blurDataURL附加到图片对象上 return { ...post, attributes: { ...post.attributes, image: { ...image, blurDataURL: base64 } } }; }) ); return ( <div className="post-list"> {processedPosts.map(post => ( <StrapiImage key={post.id} image={post.attributes.image} /> ))} </div> ); }
2. 简化后的StrapiImage组件(可作为客户端组件)
// components/StrapiImage.jsx 'use client'; // 若需客户端交互则添加,否则可省略 import Image from "next/image"; import { getStrapiMedia } from "../../lib/media"; export default function StrapiImage({ image, ...props }) { return ( <Image src={getStrapiMedia(image)} width={image.attributes.width} height={image.attributes.height} alt={image.attributes.alternativeText || ""} placeholder="blur" blurDataURL={image.attributes.blurDataURL} {...props} /> ); }
额外优化建议
- 封装生成blurDataURL的工具函数,比如
async function getBlurData(imageUrl) { ... },避免重复代码 - 若长期使用,可考虑在Strapi后端提前生成模糊占位符并存储到数据库,减少前端服务端的计算开销
内容的提问来源于stack exchange,提问作者Confuze
相关产品推荐
相关产品推荐

