Recently I've been commissioned to build a lawyer's website, to which I decided to develop using Gatsby for its Static Site Generation capability and its GraphQL API. As it is a business site, it needed to be translated to several languages - that's where I met trouble.
At this point, I had a Layout
component that I was using in every page component as a Wrapper, that was being rerendered every time the user navigated to another page. This caused some visual glitches, more specifically on some animations of the mobile navigation bar (a child of the Layout
component).
Gatsby provides certain APIs to customize building a site. I became interested in wrapPageElement
, which is a function available in both Server Rendering and Browser APIs. This function lets you wrap pages in a component of your choice - in my case, it let me wrap my pages in the Layout
component. But, of course, things are never this easy.
This site I'm building uses gatsby-plugin-react-i18next to "easily translate your Gatsby website into multiple languages" (oh, the irony). As soon as I wrapped my pages that way, I lost the translations in the Layout
component and its children, and started getting this error: warn react-i18next:: You will need to pass in an i18next instance by using initReactI18next
.
I started investigating, first with some logs here and there. I noticed that the element that I was receiving as the element in wrapPageElement
was of type I18nextProvider
, and that piqued my interest. I went to look at this plugin's source code and discovered it has an implementation of wrapPageElement
which returns wraps pages inside an i18next Context and respective provider.
Now, I know why Layout
and its children had lost their translations: they weren't able to access the Context provided by the plugin. The solution for this was clear to me then: I had to put my pages inside the Context instead of wrapping the Context in my pages!
// gatsby-ssr.js and gatsby-browser.js
const React = require('react');
const Layout = require('./src/components/Layout').default;
exports.wrapPageElement = ({ element }) => {
const newElement = React.cloneElement(
element, // I18nextProvider
element.props,
React.cloneElement(
element.props.children, // I18nextContext.Provider
element.props.children.props,
React.createElement(
Layout,
undefined,
element.props.children.props.children,
),
),
);
return newElement;
};
As a final note, do not forget that you have to use the wrapPageElement
in both SSR and Browser APIs to achieve the same result server and client-side!