Martin Malfertheiner
Martin Malfertheiner

Reputation: 363

Dynamic HTML lang property in statically generated Next.js pages

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

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.

Upvotes: 6

Views: 4031

Answers (3)

hangindev.com
hangindev.com

Reputation: 4873

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.

Upvotes: 5

iamaatoh
iamaatoh

Reputation: 796

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

Upvotes: 0

Evgeny Klimenchenko
Evgeny Klimenchenko

Reputation: 1194

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.

Upvotes: 0

Related Questions