"error while hydrating this Suspense boundary" on page with Backend data integration

Context:
I have a project page that’s getting a hydration error and mentions Suspense Boundary, and I can’t figure out why…

For context, it’s a NextJS project and uses data from a few GraphQL data queries.
I also have a “Lang” global variable for locales.

Plasmic Studio screenshot

The error seems to only occur on /history (versus /courses which also has data queries).
Screenshots from localhost:


I don’t see any errors in Plasmic Studio, so I’m not sure what’s going on. :sweat:

Hey @leafer_design,

This issue happens when the server and the client have a mismatch in the queries.
In loader we try to server side render the page, which means that we try to run the queries in the server and send the data to the client to make it a better user experience. Could it be that the server does not have some daya needed to make the graphql queries? Perhaps an auth key or something like that.

I tried duplicating your project locally in a test environment and the queries were not able to run here so I couldn’t reproduce the issue, but that probably means you need to set something up in the server to replicate the client requests behavior too.

Hi @icaro_guerra,

Thanks for looking into my issue!

That’s interesting :thinking:
I don’t believe I’ve set up anything on the server side (just the Plasmic-generated catchall, with a Lang locale global variable, and a homepage-check to add a google site verification meta).

I also set an auth key in the integration settings, so I don’t see why it would not work on that specific page :0 (the graphql queries weren’t giving me an issue on other pages)…
I’ll DM you a graphql query auth key

Hey @leafer_design, the issue might be related to the Lang variable.

In the server you need to make sure you’re trying to render the same page as the client. I think the issue is that you’re trying to render different languages in the client and in the server. Make sure you pass the correct globalVariants props in the PlasmicRootProvider in getStaticProps.

Thank you for following up!

I followed your suggestion/example on adding locale in getStaticProps (as also mentioned in this different post) but I’m still getting a suspense boundary hydration error on the /history page in localhost :cry:

It does indeed seem to be related to the Lang locale globalVariant, as I see this error on a different page in the second locale

the Lang locale global variable in Plasmic Studio

my locale setting in next.config.js

image

my [[...catchall]].tsx with locale variant declared in both PlasmicLoader and getStaticProps
import * as React from "react";
import {
  PlasmicComponent,
  extractPlasmicQueryData,
  ComponentRenderData,
  PlasmicRootProvider,
} from "@plasmicapp/loader-nextjs";
import type { GetStaticPaths, GetStaticProps } from "next";

import Head from "next/head";
import Error from "next/error";
import { useRouter } from "next/router";
import { PLASMIC } from "@/plasmic-init";

export default function PlasmicLoaderPage(props: {
  plasmicData?: ComponentRenderData;
  queryCache?: Record<string, any>;
}) {
  const { plasmicData, queryCache } = props;
  const router = useRouter();
  const { locale } = router;

  //check if home
  const isHomePage = !router.query.catchall || (Array.isArray(router.query.catchall) && router.query.catchall.length === 0);

  if (!plasmicData || plasmicData.entryCompMetas.length === 0) {
    return <Error statusCode={404} />;
  }
  const pageMeta = plasmicData.entryCompMetas[0];
  return (
    // add meta for GSC if home
    <>
      {isHomePage && (
        <Head>
          <meta name="google-site-verification" content="ABC123" />
        </Head>
      )}
      <PlasmicRootProvider
        loader={PLASMIC}
        prefetchedData={plasmicData}
        prefetchedQueryData={queryCache}
        pageRoute={pageMeta.path}
        pageParams={pageMeta.params}
        pageQuery={router.query}
        globalVariants={[{ name: 'Lang', value: locale }]}
      >
        <PlasmicComponent component={pageMeta.displayName} />
      </PlasmicRootProvider>
    </>
  );
}

export const getStaticProps: GetStaticProps = async (context) => {
  const { catchall } = context.params ?? {};
  const plasmicPath = typeof catchall === 'string' ? catchall : Array.isArray(catchall) ? `/${catchall.join('/')}` : '/';
  const plasmicData = await PLASMIC.maybeFetchComponentData(plasmicPath);
  if (!plasmicData) {
    // non-Plasmic catch-all
    return { props: {} };
  }
  const pageMeta = plasmicData.entryCompMetas[0];

  const locale = context.locale ?? "";

  // Cache the necessary data fetched for the page
  const queryCache = await extractPlasmicQueryData(
    <PlasmicRootProvider
      loader={PLASMIC}
      prefetchedData={plasmicData}
      pageRoute={pageMeta.path}
      pageParams={pageMeta.params}
      globalVariants={[{ name: 'Lang', value: locale }]}
    >
      <PlasmicComponent component={pageMeta.displayName} />
    </PlasmicRootProvider>
  );
  // Use revalidate if you want incremental static regeneration
  return { props: { plasmicData, queryCache }, revalidate: 60 };
}

export const getStaticPaths: GetStaticPaths = async () => {
  const pageModules = await PLASMIC.fetchPages();
  return {
    paths: pageModules.map((mod) => ({
      params: {
        catchall: mod.path.substring(1).split("/"),
      },
    })),
    fallback: "blocking",
  };
}

Hmm, investigating it a little bit furthur it could be an issue in our GraphQL data queries. Can you edit the queries on this page to have the Variables prop on this state and try again?

Sorry for the delay in responding,
I ended up removing variables from the graphQL query, but still am getting hydration errors.

Here are the errors I’m seeing in localhost:
Warning: forwardRef render functions accept exactly two parameters: props and ref. Did you forget to use the ref parameter?


Warning: Invalid DOM property Lang. Did you mean lang?

Warning: Prop href did not match. Server: "/history#" Client: "/en/history#

and then the hydration errors

Hey, I just checked your queries and they seem to still have the variables. Can you try again saving them with this configuration?

Can you also try creating a custom link component?

const CustomLink = React.forwardRef(function CustomLink(
  props: React.ComponentProps<typeof NextLink>,
  ref: React.Ref<HTMLAnchorElement>
) {
  if (props.href) {
    const {
      href,
      replace,
      scroll,
      shallow,
      passHref,
      prefetch,
      locale,
      ...rest
    } = props;
    const isFragment = typeof href === "string" && href.startsWith("#");
    return (
      <NextLink
        href={href}
        replace={replace}
        scroll={scroll != null ? scroll : isFragment ? false : undefined}
        shallow={shallow}
        passHref={passHref}
        prefetch={prefetch}
        locale={locale}
        {...({ legacyBehavior: true } as any)}
      >
        <a {...rest} ref={ref} />
      </NextLink>
    );
  } else {
    return <a {...props} href={undefined} ref={ref} />;
  }
});

and pass it into PlasmicRootProvider?

...
<PlasmicRootProvider ... Link={CustomLink}>
...

Oh I didn’t realize you meant the queries across the website/project

Ok, I changed the query variables to being bundled and added a custom link component in my catchall

catchall
import * as React from "react";
import {
  PlasmicComponent,
  extractPlasmicQueryData,
  ComponentRenderData,
  PlasmicRootProvider,
} from "@plasmicapp/loader-nextjs";
import type { GetStaticPaths, GetStaticProps } from "next";

import Head from "next/head";
import Error from "next/error";
import { useRouter } from "next/router";
import { PLASMIC } from "@/plasmic-init";

import Link from 'next/link';
const CustomLink = React.forwardRef(function CustomLink(
  props: React.ComponentProps<typeof Link>,
  ref: React.Ref<HTMLAnchorElement>
) {
  if (props.href) {
    const {
      href,
      replace,
      scroll,
      shallow,
      passHref,
      prefetch,
      locale,
      ...rest
    } = props;
    const isFragment = typeof href === "string" && href.startsWith("#");
    return (
      <Link
        href={href}
        replace={replace}
        scroll={scroll != null ? scroll : isFragment ? false : undefined}
        shallow={shallow}
        passHref={passHref}
        prefetch={prefetch}
        locale={locale}
        {...({ legacyBehavior: true } as any)}
      >
        <a {...rest} ref={ref} />
      </Link>
    );
  } else {
    return <a {...props} href={undefined} ref={ref} />;
  }
});

export default function PlasmicLoaderPage(props: {
  plasmicData?: ComponentRenderData;
  queryCache?: Record<string, any>;
}) {
  const { plasmicData, queryCache } = props;
  const router = useRouter();
  const { locale } = router;

  //check if home
  const isHomePage = !router.query.catchall || (Array.isArray(router.query.catchall) && router.query.catchall.length === 0);

  if (!plasmicData || plasmicData.entryCompMetas.length === 0) {
    return <Error statusCode={404} />;
  }
  const pageMeta = plasmicData.entryCompMetas[0];
  return (
    // add meta for GSC if home
    <>
      {isHomePage && (
        <Head>
          <meta name="google-site-verification" content="ZAvsTM5R7DQZbTLszHJbZZw08h2TiN00AUHiTsGlrkw" />
        </Head>
      )}
      <PlasmicRootProvider
        loader={PLASMIC}
        prefetchedData={plasmicData}
        prefetchedQueryData={queryCache}
        pageRoute={pageMeta.path}
        pageParams={pageMeta.params}
        pageQuery={router.query}
        globalVariants={[{ name: 'Lang', value: locale }]}
        Link={CustomLink}
      >
        <PlasmicComponent component={pageMeta.displayName} />
      </PlasmicRootProvider>
    </>
  );
}

export const getStaticProps: GetStaticProps = async (context) => {
  const { catchall } = context.params ?? {};
  const plasmicPath = typeof catchall === 'string' ? catchall : Array.isArray(catchall) ? `/${catchall.join('/')}` : '/';
  const plasmicData = await PLASMIC.maybeFetchComponentData(plasmicPath);
  if (!plasmicData) {
    // non-Plasmic catch-all
    return { props: {} };
  }
  const pageMeta = plasmicData.entryCompMetas[0];

  const locale = context.locale ?? "";

  // Cache the necessary data fetched for the page
  const queryCache = await extractPlasmicQueryData(
    <PlasmicRootProvider
      loader={PLASMIC}
      prefetchedData={plasmicData}
      pageRoute={pageMeta.path}
      pageParams={pageMeta.params}
      globalVariants={[{ name: 'Lang', value: locale }]}
      Link={CustomLink}
    >
      <PlasmicComponent component={pageMeta.displayName} />
    </PlasmicRootProvider>
  );
  // Use revalidate if you want incremental static regeneration
  return { props: { plasmicData, queryCache }, revalidate: 60 };
}

export const getStaticPaths: GetStaticPaths = async () => {
  const pageModules = await PLASMIC.fetchPages();
  return {
    paths: pageModules.map((mod) => ({
      params: {
        catchall: mod.path.substring(1).split("/"),
      },
    })),
    fallback: "blocking",
  };
}

Here are the errors shown in my localhost…




would this “cannot access X before initialization” error shown at build be related to the issue?

Fixed by

  • Using CustomLink in loader-nextjs PlasmicRootProvider.
  • Using the issues tab in Plasmic to find wrong kind of html element nesting.
1 Like