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
- Visit https://locize-remix-i18next-example.vercel.app/ (exact deployment of https://github.com/locize/locize-remix-i18next-example)
- Open devtools
- Check the raw response from server
- 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"?
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'
I also had the same issues when deploying on Cloudflare. Thanks @sergiodxa for your help (and this library).
Hope this can help you :
- 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.