Getting hydration error using global variants for localization?

Hey, we are getting this error on a page that use global variant for localisation :

I believe it’s because the component is loaded first server side and then client side with the different local. Could that be the issue ?

I think so, could you share more details about your project and how did you implement the localization ?

import {
PlasmicRootProvider,
PlasmicComponent,
extractPlasmicQueryData,
} from "@plasmicapp/loader-nextjs";
import { useRouter } from "next/router";
import { PLASMIC } from "../plasmic-init";

export const getStaticProps = async () => {
const plasmicData = await PLASMIC.fetchComponentData("Home");
if (!plasmicData) {
throw new Error("No Plasmic design found");
}

const compMeta = plasmicData.entryCompMetas[0];

const queryCache = await extractPlasmicQueryData(
<PlasmicRootProvider
loader={PLASMIC}
prefetchedData={plasmicData}
pageParams={compMeta.params}
>
<PlasmicComponent component={compMeta.displayName} />
</PlasmicRootProvider>
);

return {
props: {
plasmicData,
queryCache,
},
};
};

export default function Home({ plasmicData, queryCache, req }) {
console.log(req);
const router = useRouter();
const origin =
typeof window !== "undefined" && window.location.origin
? window.location.origin
: "";
const URL = ${origin};
const lang = URL.split(".").slice(-1)[0];

const compMeta = plasmicData.entryCompMetas[0];
return (
<PlasmicRootProvider
loader={PLASMIC}
prefetchedData={plasmicData}
prefetchedQueryData={queryCache}
pageParams={compMeta.params}
pageQuery={router.query}
globalVariants={[
{
name: "Locale",
value:
lang === "<http://localhost:3000>" ? "" : lang === "fr" ? "" : lang,
},
]}
>
<PlasmicComponent component={compMeta.displayName} />
</PlasmicRootProvider>
);
}

I believe its because we do the globalVariant logic only in client side and not on the queryCache so the cached version of the component and then the client version are different.

Moving the logic to the server side should fix the issue no ?

Just to clarify, how plasmic renders.
The components renders twice on page load ? A first time from the static information and a second time from the client side ?

I believe “fetch” the component data in plasmic is more correct than “render”.
There is an initial render of the component with the cachedData, and then a client side fetch of the same data ?

Okay, let me give a quick summarized explanation of the main points here:

The issue is caused by hydration, even though you server side generate the page, you still have to hydrate in the client, mainly to add event handlers/interactions.

During the hydration process Next.js checks the server side content with the client content that’s is generated so that each element is properly hydrated knowing their server side and client side version. When you code runs in server side and when is going to hydrate you probably has different values for “Locale” which makes the content different thus triggering an error.

For the fetching, if you are using queryCache which is your case, you won’t have a double fetching for the same data, but in your case your page was rendered in the server and cached based on one lang and in the client requested other, so in that case the data that you want to show may not be in the cache and you end up needing another fetch.

We have some docs on Localization https://docs.plasmic.app/learn/localization/#approach-2-use-variants and also docs on localization in nextj.js https://nextjs.org/docs/advanced-features/i18n-routing

Hi i’m working with @surviving_ferret, so there is no way to avoid this error ? it worked perfectly fine until now and we didn’t change much, i don’t really see what we can do here

and why does this problem happened in staging/prod env and not in dev ? i already had this error and it was on both env but not here, Do you have any idea why

Is this the correct way to implement variant based localization in plasmic with static rendering in Next ?

import {
PlasmicRootProvider,
PlasmicComponent,
extractPlasmicQueryData,
} from "@plasmicapp/loader-nextjs";
import { useRouter } from "next/router";
import { PLASMIC } from "../plasmic-init";

export const getServerSideProps = async (ctx) => {
const lang = ctx.req.headers.host.split(".").slice(-1)[0];
const plasmicData = await PLASMIC.fetchComponentData("Home");
if (!plasmicData) {
throw new Error("No Plasmic design found");
}

const compMeta = plasmicData.entryCompMetas[0];

const queryCache = await extractPlasmicQueryData(
<PlasmicRootProvider
loader={PLASMIC}
prefetchedData={plasmicData}
pageParams={compMeta.params}
globalVariants={[
{
name: "Locale",
value: lang === "localhost:3000" ? "" : lang === "fr" ? "" : lang,
},
]}
>
<PlasmicComponent component={compMeta.displayName} />
</PlasmicRootProvider>
);

return {
props: {
plasmicData,
queryCache,
lang,
},
};
};

export default function Home({ plasmicData, queryCache, lang }) {
const router = useRouter();
const compMeta = plasmicData.entryCompMetas[0];
return (
<PlasmicRootProvider
loader={PLASMIC}
prefetchedData={plasmicData}
prefetchedQueryData={queryCache}
pageParams={compMeta.params}
pageQuery={router.query}
globalVariants={[
{
name: "Locale",
value: lang === "localhost:3000" ? "" : lang === "fr" ? "" : lang,
},
]}
>
<PlasmicComponent component={compMeta.displayName} />
</PlasmicRootProvider>
);
}

Right now the component is always rendered with the default variant we can’t find a way to display another one

I believe the issue is only happening now on staging/production because of lang === "localhost:3000" this seems to be a check to see if the app is in dev environment.

This last snippet of code that you sent is not static generation, since you are using getServerSideProps which is server side rendering.

How are you guys deploying your application ? I wonder if you guys can use https://nextjs.org/docs/advanced-features/i18n-routing#getting-started

Here are some snippets that may help, based on the preview link as example:

next.config.js

const nextConfig = {
  reactStrictMode: false,
  i18n: {
    locales: ["es", "fr"], // This are all the localtes that you want to support
    defaultLocale: "fr", // The locale to be default
  },
};

[[..catchall]].tsx

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

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.locale ?? ""; // use the locale from the router
  if (!plasmicData || plasmicData.entryCompMetas.length === 0) {
    return <Error statusCode={404} />;
  }
  const pageMeta = plasmicData.entryCompMetas[0];
  return (
    <PlasmicRootProvider
      loader={PLASMIC}
      prefetchedData={plasmicData}
      prefetchedQueryData={queryCache}
      pageParams={pageMeta.params}
      pageQuery={router.query}
      globalVariants={[
        {
          name: "Locale",
          value: locale, // we passe the locale to the global variant
        },
      ]}
    >
      <PlasmicComponent component={pageMeta.displayName} />
    </PlasmicRootProvider>
  );
}

export const getStaticProps: GetStaticProps = async (context) => {
  const { catchall } = context.params ?? {};
  const locale = context.locale ?? ""; // Get the locale from the getStaticProps context
  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];
  // Cache the necessary data fetched for the page
  const queryCache = await extractPlasmicQueryData(
    <PlasmicRootProvider
      loader={PLASMIC}
      prefetchedData={plasmicData}
      pageParams={pageMeta.params}
      globalVariants={[
        {
          name: "Locale",
          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 {
    /**
     * Here we iterate over all the pages and generate the paths for each locale.
     * This way you can access pages in different locales, using
     *
     * /es/home
     * /fr/home
     *
     * The routes without the locale will redirect to the default locale
     * that is set in the next.config.js
     *
     * In this example, /home would have locale="fr"
     */
    paths: ["es", "fr"].flatMap((locale) => {
      return pageModules.map((mod) => ({
        params: {
          catchall: mod.path.substring(1).split("/"),
        },
        locale,
      }));
    }),
    fallback: "blocking",
  };
};

So with that in development mode you could use /fr/home to see your home page in French and /es/home to see it in Spanish, to enable it in production it depends on how your application is deployed, but you can essentially map your domain based in a locale to your-application/locale and that will be handled, let me know if this is one feasible approach for you guys ? In the link that I sent before there is a configuration for domains that may help too. We will improve our docs on localization so that this is more clear.

This is a good read too https://nextjs.org/docs/advanced-features/i18n-routing#dynamic-routes-and-getstaticprops-pages

We fixed this issue with the following code :

import {
  PlasmicRootProvider,
  PlasmicComponent,
  extractPlasmicQueryData,
} from "@plasmicapp/loader-nextjs";
import { useRouter } from "next/router";
import { PLASMIC } from "../plasmic-init";

export const getServerSideProps = async (ctx) => {
  const lang = ctx.req.headers.host.split(".").slice(-1)[0];
  const plasmicData = await PLASMIC.fetchComponentData("Home");
  if (!plasmicData) {
    throw new Error("No Plasmic design found");
  }

  const compMeta = plasmicData.entryCompMetas[0];

  const queryCache = await extractPlasmicQueryData(
    <PlasmicRootProvider
      loader={PLASMIC}
      prefetchedData={plasmicData}
      pageParams={compMeta.params}
      globalVariants={[
        {
          name: "Locale",
          value: lang,
        },
      ]}
    >
      <PlasmicComponent component={compMeta.displayName} />
    </PlasmicRootProvider>
  );

  return {
    props: {
      plasmicData,
      queryCache,
      lang,
    },
  };
};

export default function Home({ plasmicData, queryCache, lang }) {
  const router = useRouter();
  const compMeta = plasmicData.entryCompMetas[0];
  return (
    <PlasmicRootProvider
      loader={PLASMIC}
      prefetchedData={plasmicData}
      prefetchedQueryData={queryCache}
      pageParams={compMeta.params}
      pageQuery={router.query}
      globalVariants={[
        {
          name: "Locale",
          value: lang,
        },
      ]}
    >
      <PlasmicComponent component={compMeta.displayName} />
    </PlasmicRootProvider>
  );
}

It seems that the issue was caused by trying to do the logic inline in the exported component.

Thanks a lot for your help !!

Cool, just a reminder that getServerSideProps will perform SSR and the getServerSideProps will run in request-time, which I don’t know if it’s the solution that you want https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props#when-should-i-use-getserversideprops

How can I do this configuration only by plasmic studio?