reactjsnext.jsshared-state

getServerSideProps in _app.tsx in Next.js


So, here's the situation.

I'm trying to get a "language" cookie in the app initialization to change the UI accordingly. For example, if the language is "Arabic" (ar), I revert the layout to be "RTL", and vise versa.

There are two ways to achieve this, the first solution is to get the cookie inside "useEffect()", like this ...

import { parseCookies } from "nookies";
import { useEffect, useLayoutEffect, useState } from "react";
import { StyleSheetManager, ThemeProvider } from "styled-components";
import rtlPlugin from "stylis-plugin-rtl";

function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
  const [appLanguage, setAppLanguage] = useState("en");

  useEffect(() => {
    const cookies = parseCookies();
    setAppLanguage(cookies.language);
  }, []);

  useEffect(() => {
    history.scrollRestoration = "manual";

    if (appLanguage === "ar") {
      document.documentElement.classList.add("rtl");
      document.documentElement.dir = "rtl";
    } else {
      document.documentElement.classList.remove("rtl");
      document.documentElement.dir = "ltr";
    }
  }, [appLanguage]);

  console.log("Appp Language", appLanguage);

  return (
    <>
      <ThemeProvider theme={theme}>
        <StyleSheetManager stylisPlugins={[rtlPlugin]}>
          <>
            <GlobalStyle />
            <SessionProvider session={session}>
              <Component {...pageProps} />
            </SessionProvider>
          </>
        </StyleSheetManager>
      </ThemeProvider>
    </>
  );
}


export default wrapper.withRedux(MyApp);

And that works, but the issue as you can imagine is that the page flicker for a brief moment with the default "English" layout until the useEffect() is executed.

The other way to avoid such flickering behavior is of course use getInitialProps on the _app.tsx level, like this ...

MyApp.getInitialProps = async (appContext: AppContext) => {

  const cookies = nookies.get(appContext)
  const appLanguage = cookies.language;

  const appProps = await App.getInitialProps(appContext);

  return { appLanguage, ...appProps };
};

And now you have the "appLanguage" prop available to you on the root _app level to do whatever you want with it.

The only problem with this approach is that it is not recommended by the "Next.js" team to use "getInitialProps" on the _app level because according to the documentation...

"this disables the ability to perform automatic static optimization, causing every page in your app to be server-side rendered."

On the other hand, you can't seem to be able to use "getServerSideProps" on the root _app level.

So, my question is, how to have the best of both worlds? get a state to share it from the root "_app" level without using getInitialProps to not have every page server-side rendered?

Is it even possible? or is there a way to use "getServerSideProps" in the root "_app" file?

Any help is appreciated.


Solution

  • I'm not sure if this helps, but there might be a way to avoid the flicker. Instead of defaulting to English / LTR, Next.js actually has configurable i18n routing, which tries to detect the user's preferred locale using browser headers. There's even cookie support, but it has to be the hardcoded NEXT_LOCALE cookie.

    Even if you've already explored that solution, I'll just leave it here for anyone else with a similar problem.

    ============= SOLUTION USING NEXT.JS i18n routing ===========

    Thanks to @Summer advice I was able to solve my issue using Next.js built-in i18n routing solution that I was not aware it exists.

    The issue that caused the UI flicker was happening because I was waiting until the useEffect() function gets executed in _app.tsx in order to add some kind of a special CSS class or HTML attribute the tag in order to be able to target that CSS class in the CSS file and switch the direction or do any other CSS modifications.

    But now by using the built-in Next.js i18n routing, Next.js detect the language of your app, either by looking for a "NEXT_LOCALE" cookie or if he can't find that he will default to the default language of your choice, and by doing that Next.js will add a "lang" attribute automatically to your tag that comes directly from the server, and then you can target that in your CSS to switch your layout direction ...

    html {
      &[lang="ar"] {
          direction: rtl;
      }
    }
    

    And that will not cause any layout flicker.

    Thanks again to @Summer for help.

    Here's a nice article on using Next.js i18n routing. https://blog.logrocket.com/complete-guide-internationalization-nextjs/

    P.S: I decided to edit that answer to give @Summer the credit.