sergiodxa/remix-i18next

Deployment to Vercel disables SSR

nils-jansen opened this issue · 10 comments

Describe the bug

When running locally, everything works as expected and translations are rendered before they end up in the browser.
However, when deployed to Vercel, the reponse only contains the i18next keys, which are then resolved and rendered on the client.

Your Example Website or App

https://locize-remix-i18next-example.vercel.app/

Steps to Reproduce the Bug or Issue

  1. Visit https://locize-remix-i18next-example.vercel.app/ (exact deployment of https://github.com/locize/locize-remix-i18next-example)
  2. Open devtools
  3. Check the raw response from server
  4. Compare to local behaviour

Expected behavior

Content including translations should be rendered on the server.

Screenshots or Videos

No response

Platform

  • OS: Windows, Linux (n/a)
  • Browser: Chrome
  • Version: 102.0

Additional context

Not sure if this is an issue I can resolve within Vercel. If so, please point me in the right direction.

The problem is that the server config is not able to find the translation files, this usually happens in serverless platforms because they change where your files and your public files are located on the file system after deploy, so your paths in local change, and in general reading from the file system in serverless platforms like Vercel and Netlify is not safe.

I recommend you to import your translations server-side instead of reading from the file system if you deploy to serverless.

Ok, thanks for the explanation, this makes more sense now. How would I go about "importing translations server-side"?

@nils-jansen you can import it as any other module

import translations from "~/path/to/translations/lng/namespace.json"

Then pass it to the configure of i18next as resources or calling addResourceBundle.

Ok, thanks @sergiodxa. The frontend is still trying to load json files from the server as I am initializing it with the http backend.
How can I make the frontend "dumb" while still keeping Remix happy during hydration?

@nils-jansen remove the backend, you don't really need it if you setup the resources option or used addResourceBundle functionn.

When using resources, SSR works totally fine as expected.

Although when removing the backend client side, SSR of course still works fine but useTranslation always replaces the correct value with the defaultValue on the client side which is initially set to the key itself.
Is there a (clean) way to prevent useTranslation from using the defaultValue and just stay "dumb"?

@michibertel

Make sure you pass the imported file to the translation namespace, which is the default namespace, like:

 resources: {
    en: { translation: englishTranslations },
    ku: { translation: kurdishTranslations },
    ar: { translation: arabicTranslations },
  },

or if your files are managed separately for each name space per language, pass the imported namespace file to the corresponding namespace for each language, like:

 resources: {
    en: { common: commonEN, home: homeEN, about: aboutEN },
    ku: { common: commonKU, home: homeKU, about: aboutKU },
    ar: { common: commonAR, home: homeAR, about: aboutAR },
  },

@nils-jansen remove the backend, you don't really need it if you setup the resources option or used addResourceBundle functionn.

How do you add a resource? The Remix-i18next instance doesn't have that property:

Property 'addResourceBundle' does not exist on type 'RemixI18Next'
vox91 commented

I also had the same issues when deploying on Cloudflare. Thanks @sergiodxa for your help (and this library).

@designbyadrian

Hope this can help you :

  1. In i18next.server.ts
import en from "~/assets/locales/en/common.json"; //server side
import fr from "~/assets/locales/fr/common.json"; //server side

let i18next = new RemixI18Next({
    detection: {
        cookie: localeCookie,
        supportedLanguages: i18nextOptions.supportedLngs,
        fallbackLanguage: i18nextOptions.fallbackLng,
    },
    // This is the configuration for i18next used
    // when translating messages server-side only
    i18next: {
        ...i18nextOptions,
        resources: {
            "en": {
                "common": en,
            },
            "fr": {
                "common": fr,
            },
        },
    },
});`

The backend has been removed.

Then in entry.server.tsx

import en from "~/assets/locales/en/common.json"; //server side
import fr from "~/assets/locales/fr/common.json"; //server side

export default async function handleRequest(
  request: Request,
  responseStatusCode: number,
  responseHeaders: Headers,
  remixContext: EntryContext,
  // This is ignored so we can keep it in the template for visibility.  Feel
  // free to delete this parameter in your app if you're not using it!
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  loadContext: AppLoadContext
) {
  let instance = createInstance();
  let lng = await i18next.getLocale(request);

  // Check the user preferences
  let { locale } = await getLocale(request);
  if (locale) {
    lng = locale;
  }

  let ns = i18next.getRouteNamespaces(remixContext);

  await instance
    .use(initReactI18next) // Tell our instance to use react-i18next
    //.use(Backend) // Setup our backend
    .init({
      ...i18nextOptions, // spread the configuration
      lng, // The locale we detected above
      ns, // The namespaces the routes about to render wants to use
      // use resources from the server
      resources: {
        en: {
          common: en,
        },
        fr: {
          common: fr,
        },
      },
    });
   [...]
}

In entry.client.tsx, no changes : the locale files must be in the "public" folder and the backend can be used (i18next-http-backend)

@vox91 Thanks for the example code, this helped me get translations rendering on the server using Vercel.