Dynamic HTML lang property in statically generated Next.js pages

Viewed 2439

I'm working on a multilange static landing page in a Next.Js project. My goal is to have the following structure:

  • / -> English Home page
  • /de -> German Home page
  • /it -> Italian Home page

I'm building it in the following way:

pages/index.js

export default function Home() {
  return <div>English Homepage</div>
}

pages/de.js

export default function Home() {
  return <div>German page</div>
}

In order to make the website accessible, I would like to set html lang accordingly.

pages/_document.js

class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const initialProps = await Document.getInitialProps(ctx)
    return { ...initialProps }
  }

  render() {
    return (
      <Html lang={???}>
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }
}

How can I specify the language per page? I tried with getInitialProps, but that forces my website to be SSR.

3 Answers

You are indeed right about using getInitialProps. Unlike getInitialProps in normal pages that will disable Automatic Static Optimization, getInitialProps in _document.js has no such effect.

It is because Document is only rendered in the server. Document's getInitialProps function is not called during client-side transitions, nor when a page is statically optimized. More about its technical details

That's why you can use it to inject a lang prop into pages and still get the benefits of static optimization.

// _document.js
...
static async getInitialProps(ctx) {
  const initialProps = await Document.getInitialProps(ctx);
  const { pathname } = ctx;
  const lang = pathname.startsWith("/de") ? "de" : "en";
  return { ...initialProps, lang };
}
...

To have the lang attribute also updated during client-transitions, you have to also set up an useRouter hook in _app.js to watch the route change:

// _app.js
import React, { useEffect } from "react";
import { useRouter } from "next/router";

export default function MyApp({ Component, pageProps }) {
  const { pathname } = useRouter();
  const lang = pathname.startsWith("/de") ? "de" : "en";
  useEffect(() => {
    document.documentElement.lang = lang;
  }, [lang]);

  return <Component {...pageProps} />;
}

I have created this CodeSandbox for you as a demo.

Edit dynamic-html-lang-property-in-statically-generated-next-js-pages

Download it to your local machine and inspect the code. After npm install, run npm run build. You will see from the Build Logs that both "/" and "de" are static. Run npm start and view the page source, you will see the lang attribute is set properly in the HTML.

Hey maybe not the best solution, but should work, you can use dangerousAsPath prop on _document.jsx page and decide the lang based on the path.

render() {
  return (
   <Html lang={this.props.dangerousAsPath === '/de' ? 'de' : 'en'}>
     {/* ... */}
   </Html>
  )
}

Again there probably should be a better solution for this. Cheers.

Edit: There might be a chance that dangerousAsPath will be removed, if so you can use this.props.__NEXT_DATA__.page until you find a better option.

For your example, if you assume your urls will always follow this pattern https://somedomain.com/{lang}/everything-else,

then you could extract the lang from the url like:

class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const initialProps = await Document.getInitialProps(ctx)

    // `ctx.req.path` would be of pattern: `/{lang}/everything-else`
    // ctx.req.path.split('/') --> ['', 'lang', 'everything-else']
    const locale = ctx.req.path.split('/')[1]

    return { ...initialProps, locale }
  }

  render() {
    return (
      // get the language 
      <Html lang={this.props.locale}>
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }
}

As a side-note, I'd suggest exploring next-i18next package, for a more standard way of implementing localization in next.js

Related