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

NextJS部署Vercel后Storyblok预览功能异常求助

NextJS + Storyblok 部署Vercel后预览失效问题排查

问题现象

  • 本地开发时功能正常,部署到Vercel后Storyblok预览功能失效:仅在发布Storyblok内容触发Vercel全量重新部署后,修改才生效
  • 在Storyblok中移动区块后点击保存,内容会自动恢复原状
  • 浏览器使用Storyblok时出现Storefront错误,推测与Shopify API相关;环境变量配置无误,本地调试无异常,Vercel日志未发现问题

相关代码

Storefront函数代码

//Assigning Storefront access token and the shop URL
const storefrontAccessToken: any =
  process.env.NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN;
const storeURL: any = process.env.NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN;

export async function storefront(query: any, variables = {}) {
  try {
    const response = await fetch(storeURL, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "X-Shopify-Storefront-Access-Token": storefrontAccessToken,
      },
      body: JSON.stringify({ query, variables }),
    });
    const JSONResponse = await response.json();
    return JSONResponse;
  } catch (error: any) {
    console.error("Storefront Error:", error);
  }
}

index.tsx代码

export default function Home({ story, products }: any) {

  //Enable the Visual Editor
  story = useStoryblokState(story);

  return (
    <>
      <ProductsProvider value={products}>
        <StoryblokComponent blok={story.content} />
      </ProductsProvider>
    </>
  )
}

export async function getStaticProps() {
  const storyblokApi = getStoryblokApi();
  //home is the default slug for the homepage in Storyblok
  let slug = 'home';

  //Load draft version
  let sbParams: any = {
    version: "draft" //Or 'published
  };
  // Recieve storyblok data
  let { data }: any = await storyblokApi.get(`cdn/stories/${slug}`, sbParams);
  // Recieve shopify data
  const shopifyData: any = await storefront(productsQuery);

  return {
    props: {
      //Storyblok
      story: data ? data.story : false,
      key: data ? data.story.id : false,
      //Shopify
      products: shopifyData.data,
    },
    revalidate: 3600,
  }
}

问题原因分析

  1. 静态生成缓存限制
    你使用了getStaticProps+revalidate的增量静态再生(ISR),但Storyblok预览需要实时获取draft版本内容。ISR默认会缓存页面,getStaticProps仅在构建或重新验证时执行,Storyblok编辑器的实时修改无法触发页面重新验证,导致预览看不到最新内容。

  2. Shopify API的客户端暴露问题
    你给Shopify的环境变量加了NEXT_PUBLIC_前缀,这会把密钥暴露给客户端。生产环境中,客户端直接请求Shopify API会触发跨域限制(本地开发有CORS代理,生产环境没有),同时暴露密钥存在安全风险。这个API请求错误会导致Storyblok编辑器的内容同步逻辑中断,出现区块保存后恢复的问题。

  3. Storyblok编辑器的错误依赖
    Storyblok编辑器依赖前端无错误的运行状态来同步内容修改。一旦前端出现API请求错误(比如Shopify请求失败),编辑器会无法确认内容已在前端渲染,从而回滚到修改前的状态。

修复方案

1. 适配Storyblok预览的动态内容获取

修改getStaticProps,根据预览模式切换内容版本并调整缓存策略:

export async function getStaticProps(context) {
  const storyblokApi = getStoryblokApi();
  let slug = 'home';
  // 判断是否处于Storyblok预览模式
  const isPreview = context.preview || false;
  let sbParams: any = {
    version: isPreview ? "draft" : "published"
  };
  let { data }: any = await storyblokApi.get(`cdn/stories/${slug}`, sbParams);
  const shopifyData: any = await storefront(productsQuery);

  return {
    props: {
      story: data ? data.story : false,
      key: data ? data.story.id : false,
      products: shopifyData.data,
      isPreview,
    },
    revalidate: isPreview ? 0 : 3600, // 预览模式禁用缓存
  }
}

同时在Storyblok后台配置预览URL为Vercel的预览分支地址,确保预览模式能正确拉取draft内容。

2. 修复Shopify API的权限与跨域问题

  • 移除Shopify环境变量的NEXT_PUBLIC_前缀,改为服务端仅能访问的变量:
    const storefrontAccessToken: string = process.env.SHOPIFY_STOREFRONT_ACCESS_TOKEN!;
    const storeURL: string = process.env.SHOPIFY_STORE_DOMAIN!;
    
  • 确保所有Shopify API请求都在服务端执行(如getStaticProps/getServerSideProps),不要在客户端组件中直接调用storefront函数。若客户端需要Shopify数据,通过组件Props传递,或创建Next.js API路由作为中间层,客户端通过API路由请求数据。

3. 添加错误边界避免编辑器同步失败

给Storyblok组件添加错误边界,防止单个API错误导致整个编辑器逻辑中断:

// 示例错误边界组件
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    console.error("Storyblok Component Error:", error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return <div>内容加载出现问题,请重试</div>;
    }

    return this.props.children;
  }
}

// 在页面中使用
export default function Home({ story, products }: any) {
  story = useStoryblokState(story);

  return (
    <>
      <ProductsProvider value={products}>
        <ErrorBoundary>
          <StoryblokComponent blok={story.content} />
        </ErrorBoundary>
      </ProductsProvider>
    </>
  )
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 19:59:53