1

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.

juliomalves
  • 42,130
  • 20
  • 150
  • 146
Ruby
  • 2,207
  • 12
  • 42
  • 71

2 Answers2

3

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.

Ruby
  • 2,207
  • 12
  • 42
  • 71
Summer
  • 1,175
  • 8
  • 18
  • Thank you. I'll try that out. Currently, I'm handling the locale by placing all of my pages inside `/[lang]/` dynamic route. So it can be `/en/somepage/`, or `/ar/somepage/`. Will try the i18n routing solution and see if it helps. – Ruby Jan 07 '22 at 19:14
  • 1
    This actually helped a lot. I refactored my app to use the built-in Next.js i18n routing, and that made the code a lot more cleaner, the neat thing is it solved my issue for the "rtl" flicker because Next.js add the lang attribute to the tag right from the start (SSR), and I was able to target that in my CSS directly to switch the direction, instead of adding any class manually to the HTML tag in `useEffect`. Thank you for the help. – Ruby Jan 08 '22 at 12:16
  • @Ruby That edit should really have been a new answer, those are your words not Summer's. If you want to credit this answer simply mention and link to it in your own answer. – juliomalves Jan 09 '22 at 15:25
  • @juliomalves That's OK. The main idea here is to help others with a similar issue. – Ruby Jan 10 '22 at 14:09
0

Using getServerSideProps() on _app.tsx is not currently supported.

You can follow the discussion on Github: https://github.com/vercel/next.js/discussions/10874

Camilo
  • 6,504
  • 4
  • 39
  • 60