Using a MyComponent with withNamespaces("common")(MyComponent) inside <Head> of next/head triggers an errors:
TypeError: Cannot read property 'wait' of null
at NamespacesConsumerComponent.render (my-project/node_modules/react-i18next/dist/commonjs/NamespacesConsumer.js:213:33)
Any version.
withNamespaces:import React, { Component } from "react";
export const MetaOgTag = withNamespaces("common")(
class extends Component {
render() {
const { title } = this.props;
return (
<>
<meta property="og:title" content={t(title)} />
</>
)
}
}
);
pages/index.js inside <head>...</head>:import React from "react";
import { MetaOgTag } from "../components/MetaOgTag";
import Head from "next/head";
export default class Index extends React.Component {
render() {
return (
<Head>
<MetaOgTag title={"Hello World"} />
</Head>
);
}
}
It should not crash.
None.
It looks like it's not possible to use i18n inside head because something not ready/setup yet.
Hi @Nelrohd - looks like that error is coming out of react-i18next. Most likely, req.i18n is still null. Did you debug any further?
Seems like i18n from React context is not available inside <Head />, so withNamespaces cannot get it. In one of my projects I just pass t as a prop and all works. When I need to reference a string in the common namespace I prefix the id with it, e.g. t("common:foobar").
Feel free to copy the component code (it's written in TypeScript).
import _ from "lodash";
import Head from "next/head";
import { useContext } from "react";
import { format, parse } from "url";
import { TFunction } from "../../i18n";
import { SsrUrlContext } from "../../lib/appContext";
interface Props {
t: TFunction;
title?: string | false;
titleSuffix?: string | false;
description?: string | false;
keywords?: string | false;
keywordsSuffix?: string | false;
canonicalUrl?: string | false;
supportedGetParamsInCanonicalUrl?: string[];
}
const PageMeta = ({
t,
title,
titleSuffix,
description,
keywords,
keywordsSuffix,
canonicalUrl,
supportedGetParamsInCanonicalUrl,
}: Props) => {
const ssrUrl = useContext(SsrUrlContext);
let derivedCanonicalUrl;
if (canonicalUrl) {
derivedCanonicalUrl = canonicalUrl;
} else if (canonicalUrl !== false) {
const { port, query, ...rest } = parse(
typeof window !== "undefined" ? window.location.href : ssrUrl,
true,
);
const cleanedQuery = _.pick(query, supportedGetParamsInCanonicalUrl || []);
derivedCanonicalUrl = format({
...rest,
hostname: undefined,
host: undefined,
protocol: undefined,
search: undefined,
query: _.fromPairs(_.orderBy(_.toPairs(cleanedQuery), (pair) => pair[0])),
hash: undefined,
}).replace("//", "");
}
const derivedTitleSuffix =
titleSuffix !== false ? titleSuffix || t("common:pageTitleSuffix") : "";
const derivedTitle = title !== false ? title || t("pageTitle") : null;
const derivedDescription =
description !== false ? description || t("pageDescription") : undefined;
const derivedKeywordsSuffix =
keywordsSuffix !== false && keywords !== false
? keywordsSuffix || t("common:pageKeywordsSuffix")
: "";
const derivedKeywords =
keywords !== false ? keywords || t("pageKeywords") : null;
return (
<Head>
<title>
{derivedTitle}
{derivedTitleSuffix}
</title>
<meta name="description" content={derivedDescription} />
<meta
name="keywords"
content={`${derivedKeywords}${derivedKeywordsSuffix}`}
/>
{derivedCanonicalUrl ? (
<link rel="canonical" href={derivedCanonicalUrl} />
) : null}
</Head>
);
};
export default PageMeta;
On most pages you just use <PageMeta t={t} /> and the component will pick the right metadata as long as you page uses withNamespaces("pageName"). On the home page you might want to use <PageMeta t={t} titleSuffix={false} /> if you want to see My awesome website instead of Homepage – my awesome website. On a product / blog post page this would be something like <PageMeta t={t} title={product.title} description={product.description} />.
That must mean that NextJs is putting the Head component somewhere outside our i18n provider.
@Nelrohd I have a professional project wherein we're localising next/head inside our _app.tsx as such:
Head.tsx
import NextHead from 'next/head';
import * as React from 'react';
import { withNamespaces } from '../../i18n';
const Head = ({ t }) => (
<NextHead>
<title>{t('page.title')}</title>
</NextHead>
);
export default withNamespaces('common')(Head);
_app.tsx
import * as React from 'react';
import NextApp, { Container } from 'next/app';
import { Head } from '../components';
import { appWithTranslation } from '../i18n';
class App extends NextApp {
render() {
const { Component, pageProps } = this.props;
return (
<Container>
<Head />
<Component {...pageProps} />
</Container>
);
}
}
export default appWithTranslation(App);
Not sure exactly what is going wrong for you, but I can confirm that this approach works just fine.
@isaachinman Your example is different from mine.
In my example, my component use withNamespaces and is placed inside next/head.
In your example you create a component with withNamespaces and put its content inside next/head
Yes I understand that. Clearly NextJs is lifting next/head into the head and out of the React tree. The approach I showed works - is it possible for you to refactor to this?
@isaachinman For sure, I already did it before in fact but I wanted to point the issue to help others and maybe have a fix if it's possible.
I don't think there's going to be a possible fix outside of wrapping as I showed above. It's an acceptable solution and works just fine, though. Happy to continue discussion if anyone feels it's necessary.
I also faced with that issue. This issue should be pinned in the readme :smile:
Another solution is to wrap your tags in your component with a next/head component instead to centralize them in a single next/head at the end next will hoist them all together.
import React, { Component } from "react";
import Head from 'next/head';
export const MetaOgTag = withNamespaces("common")(
class extends Component {
render() {
const { title } = this.props;
return (
<Head>
<meta property="og:title" content={t(title)} />
</Head>
)
}
}
);
import React from "react";
import { MetaOgTag } from "../components/MetaOgTag";
import Head from "next/head";
export default class Index extends React.Component {
render() {
return (
<Head>
<title>Hello World</title>
</Head>
<MetaOgTag title={"Hello World"} />
);
}
}
@StarpTech #286.
Most helpful comment
@Nelrohd I have a professional project wherein we're localising
next/headinside our_app.tsxas such:Head.tsx
_app.tsx
Not sure exactly what is going wrong for you, but I can confirm that this approach works just fine.