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

Next.js 13与Storyblok循环依赖问题求助

解决Next.js 13 + Storyblok循环依赖导致的预渲染错误

问题根源

你遇到的ReferenceError: Cannot access '__WEBPACK_DEFAULT_EXPORT__' before initialization确实是循环依赖导致的:

  • component-list.js导入所有业务组件并导出ComponentsMap
  • StoryblokComponent.js依赖ComponentsMap匹配渲染组件
  • 业务组件(如Grid)又依赖StoryblokComponent渲染嵌套内容
    三者形成引用闭环,导致Webpack在预渲染阶段无法正确初始化模块。

解决方案1:动态导入组件(推荐)

通过动态导入打破循环依赖,不在component-list中直接导入所有组件,而是在需要时按需加载:

1. 重构component-list.js

将静态导入改为动态导入的函数:

// components-list.js
export const getComponent = async (componentKey) => {
  switch(componentKey) {
    case 'page':
      return (await import('@components/contentTypes/Page')).default;
    case 'news':
      return (await import('@components/contentTypes/News')).default;
    case 'newsArticle':
      return (await import('@components/contentTypes/NewsArticle')).default;
    case 'grid':
      return (await import('./grid/Grid')).default;
    case 'feature':
      return (await import('./feature/Feature')).default;
    case 'teaser':
      return (await import('./teaser/Teaser')).default;
    case 'config':
      return (await import('./config/Config')).default;
    case 'menu_header':
      return (await import('./config/MenuHeader')).default;
    case 'menu_link':
      return (await import('./config/MenuLink')).default;
    case 'menu_group':
      return (await import('./config/MenuGroup')).default;
    default:
      console.error(`Component ${componentKey} doesn't exist.`);
      return null;
  }
}

2. 修改StoryblokComponent.js使用React.lazy和Suspense

利用React的懒加载能力处理动态导入的组件:

// StoryblokComponent.js
import React, { lazy, Suspense } from 'react';
import { getComponent } from './components-list';

const StoryblokComponent = ({ blok, ...restProps }) => {
  if (!blok) {
    console.error("Please provide a 'blok' property to the StoryblokComponent");
    return <div>Please provide a blok property to the StoryblokComponent</div>;
  }

  const LazyComponent = lazy(() => 
    getComponent(blok.component).then(comp => ({ default: comp }))
  );

  return (
    <Suspense fallback={<div>Loading...</div>}>
      <LazyComponent blok={blok} {...restProps} />
    </Suspense>
  );
};

export default StoryblokComponent;

3. 业务组件无需修改

原来的Grid等组件保持原样即可,动态导入会自动打破循环依赖。

解决方案2:通过props传递渲染函数规避直接导入

如果不想用动态导入,可以让业务组件不直接导入StoryblokComponent,而是通过props接收渲染函数:

1. 修改业务组件(以Grid为例)

移除StoryblokComponent的导入,改用props传入的renderBlok函数:

// Grid.js
import { storyblokEditable } from '@storyblok/js';

const Grid = ({ blok, renderBlok }) => {
  return (
    <div {...storyblokEditable(blok)}>
      {blok.columns.map((nestedBlok) => (
        <div key={nestedBlok._uid}>
          {renderBlok(nestedBlok)}
        </div>
      ))}
    </div>
  );
};

export default Grid;

2. 修改StoryblokComponent.js传递渲染函数

在渲染组件时,将自身作为renderBlok传递给子组件:

// StoryblokComponent.js
import React from 'react';
import { ComponentsMap } from './components-list';

export const StoryblokComponent = ({ blok, ...restProps }) => {
  if (!blok) {
    console.error("Please provide a 'blok' property to the StoryblokComponent");
    return <div>Please provide a blok property to the StoryblokComponent</div>;
  }

  if (blok.component) {
    const Component = ComponentsMap[blok.component];
    if (Component) {
      return (
        <Component 
          blok={blok} 
          renderBlok={(childBlok) => <StoryblokComponent blok={childBlok} {...restProps} />} 
          {...restProps} 
        />
      );
    }
  }
  return <> </>;
};

注意:此方法需确保所有嵌套组件都接受renderBlok props,且Next.js预渲染时可能需要额外处理序列化问题,不如动态导入方案通用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 22:53:13