Api Call Inside GetStaticProps in [[...pages]].jsx Not Working in Builder Preview

Hey there ,

I am making an API call to fetch product data using Shopify Storefront GraphQL request that i am making inside getStaticProps and then inserting inside builder component props .
While it is working if i publish the page in Builder and checking in front end , but does not work on preview inside Builder or from the preview link .

I am pretty sure you would have got this question already , however could not find any related post . Let me know if you have any quick solution to this , happy to provide more details .

Hi @srittam,

Welcome to the builder.io forum.

Can you share [[…page]].jsx code?

========= [[…page]].jsx ============

import { BuilderComponent, builder } from "@builder.io/react";
import { useEffect, useState } from "react";

import Footer from "@/components/Footer";
import Head from "@/components/Head";
import fallbacks from "@/utils/fallbacks";
import getCommonProps from "@/utils/getCommonProps";
import { getCookie } from "@/lib/gtm";

import TextOverlayOnImage from "../blocks/TextOverlayOnImage";
import ThreeProductGrid from "../blocks/ThreeProductGrid";
import { useAddToCartContext } from "@/context/Store";
import "../utils/widgets";
import { processPageData } from '@/utils';

// put your Public API Key you copied from Builder.io here
//builder.init(process.env.BUILDER_API_KEY)
builder.init(process.env.NEXT_PUBLIC_BUILDER_API_KEY);

export async function getStaticProps(context) {
  // Page Content
  const page =
    (await builder
      .get("page", {
        userAttributes: {
          urlPath: "/" + (context.params?.page?.join("/") || ""),
        },
      })
      .toPromise()) || null;
  
  const processedPage = await processPageData(page)

  return {
    props: {
      page: processedPage,
      ...(await getCommonProps(context)),
    },
    revalidate: 5,
  };
}

export async function getStaticPaths() {
  const pages = await builder.getAll("page", {
    options: { noTargeting: true },
  });

  return {
    paths: pages.map((page) => `${page.data?.url}`),
    fallback: true,
  };
}
let addToCart = null

const onProductRedemption = async(data) => {
  function calcShopifyToken(point_redemption_id, variant_id) {
    //Fill in your loyalty api key in the line below
    let api_key=""
    let buckets = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
    let raw_token = reverseString(variant_id.toString()) + reverseString(api_key) + reverseString(point_redemption_id.toString())
    let token = ""
    let rotate_amount = 1 + (point_redemption_id % 59)
    for (let i = 0; i < raw_token.length; i++) {
    let ord = raw_token.charCodeAt(i);
    let orig_index = false;
    if (ord >= 65 && ord <= 90)
    orig_index = ord-65
    else if (ord >= 97 && ord <= 122)
    orig_index = ord - 97 + 26
    else if (ord >= 48 && ord <= 57)
    orig_index = ord - 48 + 52
    if (orig_index) {
    let new_index = (orig_index + rotate_amount) % 62;
    token += buckets[new_index];
    }
    }
    return token;
    }
    function reverseString(text) {
    let text_as_array = text.split("");
    let reverse_array = text_as_array.reverse();
    let reverse_text = reverse_array.join("");
    return reverse_text;
    }

  const {token,id,variantId,costInPoints} = data;

  const generatedToken = (calcShopifyToken(id,variantId)).slice(0, 14);

  console.log(generatedToken, "generated token")
  //call existing add to cart flow
  if(token.indexOf(generatedToken) > -1){
    addToCart({
      variantId: 'gid://shopify/ProductVariant/' + data.variantId,
      attributes: [
        {
            key: "_swell_discount_type",
            value: "product"
        },
        {
          key: "_swell_redemption_id",
          value: `${id}`
        },
        {
          key: "_swell_redemption_token",
          value: `${token}`
        },
        {
          key: "_swell_discount_amount_cents",
          value: "2500"
        },
        {
          key: "_swell_loyalty_points",
          value: `${costInPoints}`
        },
    ],
      variantQuantity: 1})
    return true
  } else {
    return false
  }
}

export default function Page(commonProps) {
  addToCart = useAddToCartContext();
  const [userData, setUserData] = useState({});
  useEffect(() => {
    console.log('commonProps==>',commonProps)
    if(typeof window != undefined) {
      setTimeout(() => {
        window.yotpoWidgetsContainer?.initWidgets()
      },2000)
      window.onProductRedemption = onProductRedemption;
    }
    const userData = JSON.parse(getCookie('userData') ? decodeURIComponent(getCookie('userData')) : JSON.stringify({}));
    const {customer_email = '' , customer_id = ''} = userData || {};
    const userDataObj = {
      customer_email,
      customer_id,
      auth: false
    }
    if(customer_email.length > 0) {
      userDataObj.auth = true;
    }
    setUserData(userDataObj);
  }, []) 

  const fb = fallbacks(commonProps);
  
  
  if (fb) return fb;

  return (
    <>
      <Head {...commonProps} />

      <main
        className={commonProps.page?.data.headerBg ? "md:-mt-[6.5rem]" : ""}
      >
        <BuilderComponent model="page" content={commonProps.page} />
        <div 
        id="swell-customer-identification"
        data-authenticated={userData.auth}
        data-email={userData.customer_email}
        data-id={userData.customer_id}
        style={{display: 'none'}}>
        </div>
      </main>

      <Footer {...commonProps} />
      {/* <TextOverlayOnImage />
      <ThreeProductGrid /> */}
    </>
  );
}




========= [[…page]].jsx End ============

So under getStaticProps function , you will find a function called processPageData inside which i am making the API call and inserting the responce in one of the builder component called productColumn .

======processPageData Function : i am modifying the component here ========

import componentAlternation from './componentAlteration'

export async function processPageData (page) {
    const allComp = page?.data?.blocks;
    if(!allComp.length) return page;


    const compNames = allComp.map(comp => comp?.component?.name).filter(Boolean);
    const compoLoader = [];

    compNames.forEach(block => {
        const compfun = componentAlternation[block];
        if(!compfun) return;

        compoLoader.push(compfun(page));
    })

    const resolvedComponents = await Promise.all(compoLoader);

    resolvedComponents.forEach(compBlock => {
      const blockIndex = page?.data?.blocks?.findIndex(block => block?.component?.name === compBlock?.component?.name);
      page.data.blocks[blockIndex] = compBlock;
    })

    return page;
    
}

===== componentAlternation Function : here i am making the Shopify graphQL api call =====

import {getCollectionMetafields} from '@/lib/shopify'

const componentBlock = (pageData,compName) => {
    const blocks = pageData?.data?.blocks
    if(!blocks.length) return;

    return blocks.find(comp => comp.component.name === compName )
}

const processProductData = (itesm) => {
    return itesm.edges.map(prd => {
        const prdVariant = prd?.node?.variants?.edges[0]?.node;
        const image = prd?.node?.images?.edges[0]?.node;
        const subsdata = prd?.node?.sellingPlanGroups.edges;
        let sellingPlanGroups

        if(!subsdata.length){
            sellingPlanGroups = false
        }else {
            const group = subsdata[0]?.node;
            const sellingPlans = group?.sellingPlans?.edges?.map(plan => plan?.node);
            sellingPlanGroups = {...group,sellingPlans}
        }

        return {...prd.node,variants:prdVariant,images:image,sellingPlanGroups};

    })
  }

export default {
    async ProductColumn(pageData) {
        const componentDetails = componentBlock(pageData,'ProductColumn');  
        const collectionHandle = componentDetails?.component?.options?.product_collection_handle;
        if(!collectionHandle) return componentDetails;
        
        try{
            const collectionData = await getCollectionMetafields(collectionHandle);
            if(!collectionData.data.collectionByHandle) {
                console.log('wrong product handle added in Builder');
                return componentDetails
            }

            const products = collectionData?.data?.collectionByHandle?.products;
            componentDetails.component.options['collectionProducts'] = processProductData(products);
        }catch(err){
            console.err('error while fetching collection data:',err);
        }finally {
            return componentDetails
        }
    }
}


Let me know if you need more info .

@manish-sharma any update on above ?

Hi @srittam,

Would you be able to confirm if you are getting any errors in the console?

@manish-sharma nothing that sort which is related to this issue .

Bumping this… push project from local to github, trying to deploy to production on vercel. getting the following error:

[09:47:52.165] Running build in Washington, D.C., USA (East) – iad1
[09:47:52.225] Cloning github.com/psychicengineering/nextjs12 (Branch: main, Commit: 8ae8370)
[09:47:52.529] Previous build cache not available
[09:47:52.662] Cloning completed: 436.218ms
[09:47:52.878] Running "vercel build"
[09:47:53.567] Vercel CLI 29.2.0
[09:47:53.880] Installing dependencies...
[09:48:06.246] 
[09:48:06.246] added 352 packages in 12s
[09:48:06.246] 
[09:48:06.247] 127 packages are looking for funding
[09:48:06.247]   run `npm fund` for details
[09:48:06.271] Detected Next.js version: 13.4.2
[09:48:06.282] Detected `package-lock.json` generated by npm 7+...
[09:48:06.282] Running "npm run build"
[09:48:06.687] 
[09:48:06.687] > nextjs12@0.1.0 build
[09:48:06.687] > next build
[09:48:06.687] 
[09:48:07.257] Attention: Next.js now collects completely anonymous telemetry regarding usage.
[09:48:07.258] This information is used to shape Next.js' roadmap and prioritize features.
[09:48:07.258] You can learn more, including how to opt-out if you'd not like to participate in this anonymous program, by visiting the following URL:
[09:48:07.258] https://nextjs.org/telemetry
[09:48:07.258] 
[09:48:07.387] - info Linting and checking validity of types...
[09:48:10.500] Failed to compile.
[09:48:10.500] 
[09:48:10.501] ./pages/[[...page]].tsx:12:40
[09:48:10.501] Type error: Binding element 'params' implicitly has an 'any' type.
[09:48:10.501] 
[09:48:10.501] e[0m e[90m 10 | e[39me[90m// Define a function that fetches the Builder e[39me[0m
[09:48:10.501] e[0m e[90m 11 | e[39me[90m// content for a given pagee[39me[0m
[09:48:10.501] e[0me[31me[1m>e[22me[39me[90m 12 | e[39me[36mexporte[39m e[36masynce[39m e[36mfunctione[39m getStaticProps({ params }) {e[0m
[09:48:10.502] e[0m e[90m    | e[39m                                       e[31me[1m^e[22me[39me[0m
[09:48:10.502] e[0m e[90m 13 | e[39m  e[90m// Fetch the builder content for the given pagee[39me[0m
[09:48:10.502] e[0m e[90m 14 | e[39m  e[36mconste[39m page e[33m=e[39m e[36mawaite[39m buildere[0m
[09:48:10.502] e[0m e[90m 15 | e[39m    e[33m.e[39me[36mgete[39m(e[32m'page'e[39me[33m,e[39m {e[0m
[09:48:10.560] Error: Command "npm run build" exited with 1
[09:48:11.286] Deployment completed
[09:48:10.860] BUILD_UTILS_SPAWN_1: Command "npm run build" exited with 1

Hi @psychic.engineering,

You will need to define type for params and page

Please refer to below code for reference

import React from 'react';
import { useRouter } from 'next/router';
import { BuilderComponent, builder, useIsPreviewing } from '@builder.io/react';
import DefaultErrorPage from 'next/error';
import Head from 'next/head';

// Replace with your Public API Key
builder.init("API_KEY");

export async function getStaticProps({ params }: any) {
  // Fetch the builder content
  const page = await builder
    .get('page', {
      userAttributes: {
        urlPath: '/' + (params?.page?.join('/') || ''),
      },
    })
    .toPromise();

  return {
    props: {
      page: page || null,
    },
    revalidate: 5
  };
}

export async function getStaticPaths() {
  // Get a list of all pages in builder
  const pages = await builder.getAll('page', {
    // We only need the URL field
    fields: 'data.url', 
    options: { noTargeting: true },
  });

  return {
    paths: pages.map(page => `${page.data?.url}`),
    fallback: true,
  };
}

export default function Page({ page }: any) {
  const router = useRouter();
  const isPreviewing = useIsPreviewing();

  if (router.isFallback) {
    return <h1>Loading...</h1>
  }

  if (!page && !isPreviewing) {
    return <DefaultErrorPage statusCode={404} />
  }

  return (
    <>
      <Head>
        <title>{page?.data.title}</title>
      </Head>
      {/* Render the Builder page */}
      <BuilderComponent model="page" content={page} />
    </>
  );
}

Hi @srittam,

We are not able to reproduce this issue, you may find help at our NextJS + Shopify starter