You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React中Contentful富文本渲染异步获取嵌入资产报错解决

解决React中使用Contentful documentToReactComponent异步加载图片资产的问题

你遇到的错误核心是:documentToReactComponent的渲染节点函数需要同步返回React元素,但你直接在里面执行了异步请求并返回Promise,而Promise不是有效的React子元素。下面提供两种可行的解决方案:

方案一:封装异步图片组件(推荐)

把异步获取图片资产的逻辑封装到独立组件中,利用React的状态管理处理加载、成功、失败的状态:

import { useState, useEffect } from 'react';

// 自定义异步图片组件
const ContentfulImage = ({ assetId, width, height }) => {
  const [imageData, setImageData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchAsset = async () => {
      try {
        const asset = await client.getAsset(assetId);
        setImageData({
          url: asset.fields.file.url,
          alt: asset.fields.title || asset.fields.file.fileName
        });
      } catch (err) {
        setError(err);
        console.error('加载图片资产失败:', err);
      } finally {
        setLoading(false);
      }
    };

    fetchAsset();
  }, [assetId]);

  if (loading) return <div className="image-loading">加载中...</div>;
  if (error) return <div className="image-error">图片加载失败</div>;
  if (!imageData) return null;

  return (
    <Image
      width={width}
      height={height}
      src={`https://${imageData.url}`}
      alt={imageData.alt}
    />
  );
};

// 修改富文本渲染配置
const RICHTEXT_OPTIONS = {
  renderNode: {
    [BLOCKS.PARAGRAPH]: (node, children) => <p>{children}</p>,
    [INLINES.HYPERLINK]: (node, children) => <a href={node?.data.uri}>{children}</a>,
    [BLOCKS.EMBEDDED_ASSET]: (node) => {
      const id = node?.data.target.sys.id;
      return <ContentfulImage assetId={id} width={400} height={400} />;
    },
  },
};

// 组件保持不变
function ClickedArticle({ article }) {
 const { title, date, author, Img, content } = article;
 return (
        <div className={styles.clickedArticle__article__content}>
         {documentToReactComponents(content, RICHTEXT_OPTIONS)}
        </div>
 )
}

这种方案的优势是逻辑解耦,每个图片独立处理自身的加载状态,无需提前收集资产ID,实现简单。

方案二:预加载所有图片资产

如果文章中的图片数量较多,可以提前批量获取所有资产信息,减少请求次数:

import { useState, useEffect } from 'react';

function ClickedArticle({ article }) {
 const { title, date, author, Img, content } = article;
 const [assetsMap, setAssetsMap] = useState({});

 // 收集内容中所有嵌入的资产ID
 const collectAssetIds = (contentDoc) => {
  const ids = [];
  const traverse = (node) => {
    if (node.nodeType === BLOCKS.EMBEDDED_ASSET) {
      ids.push(node.data.target.sys.id);
    }
    if (node.content) {
      node.content.forEach(traverse);
    }
  };
  contentDoc.content.forEach(traverse);
  return ids;
 };

 useEffect(() => {
  const assetIds = collectAssetIds(content);
  if (assetIds.length === 0) return;

  // 批量获取所有资产
  const fetchAssets = async () => {
    const assets = await Promise.all(assetIds.map(id => client.getAsset(id)));
    const assetMap = assets.reduce((acc, asset) => {
      acc[asset.sys.id] = {
        url: asset.fields.file.url,
        alt: asset.fields.title || asset.fields.file.fileName
      };
      return acc;
    }, {});
    setAssetsMap(assetMap);
  };

  fetchAssets();
 }, [content]);

 // 动态生成富文本渲染配置
 const RICHTEXT_OPTIONS = {
  renderNode: {
    [BLOCKS.PARAGRAPH]: (node, children) => <p>{children}</p>,
    [INLINES.HYPERLINK]: (node, children) => <a href={node?.data.uri}>{children}</a>,
    [BLOCKS.EMBEDDED_ASSET]: (node) => {
      const id = node?.data.target.sys.id;
      const imageData = assetsMap[id];
      if (!imageData) return <div>加载中...</div>;
      return (
        <Image
          width={400}
          height={400}
          src={`https://${imageData.url}`}
          alt={imageData.alt}
        />
      );
    },
  },
 };

 return (
        <div className={styles.clickedArticle__article__content}>
         {documentToReactComponents(content, RICHTEXT_OPTIONS)}
        </div>
 )
}

这种方案通过批量请求提升性能,适合图片密集的内容场景,避免多次单个请求。

内容的提问来源于stack exchange,提问作者Redhewlett

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 15:10:33