Supporting multiple languages in a PWA — Part Four — Let search engines know your supported languages

3 min readFeb 1, 2020


This is part four of this blog post. Here’s links to part one, two and three.

Tell Google your page is available in other languages

Even though Google’s web crawler is quite smart and should find the domains for each language, it helps if you tell Google you have different translations for your web page.

If you follow Google’s documentation, you see that on the English version of your site, you can add a link tag that tells Google there is a Dutch version:

<link rel="alternate" hreflang="nl" href="" />

NOTE: For those who didn’t follow part 1–3, I pretend that localhost is an english domain like and is a Dutch domain like This way you can follow this tutorial without having real domains.

And a link tag in the Dutch version of your site that tells Google there is an English version:

<link rel="alternate" hreflang="en" href="http://localhost/" />

So how do we make sure this gets generated automatically? Especially if you support more than two languages. Below you can find out how I did this with react-helmet.

Getting the code

If you didn’t follow the first three blog posts, you can download the following the code so fat from Github. Do a git clone and then switch to the 3.0 tag by running: git checkout tags/3.0

I extended (new code is bold) my PageHelmet.tsx file:

import React from "react";
import { Helmet } from "react-helmet";

type PageHelmetProps = {
pageTitle: string,
pageDescription: string

const PageHelmet: React.FC<PageHelmetProps> = ({ pageTitle, pageDescription}) => {
const locale = window.location.hostname === "" ? 'en' : 'nl';
const hostname = window.location.hostname === "" ? 'http://localhost' : '';

return (
<meta name="description" content={pageDescription} data-testid="metaDescription" />
<link rel="alternate" href={hostname + document.location.pathname} hrefLang={locale} />

We can remove the PageHelmet it from the App.tsx as the views (Home.tsx, News.tsx and About.tsx) already contain the PageHelmet.tsx, :

The result:

When you browse to the other pages, the alternate url changes along.

Now if we go to the Dutch version, we see that the alternate link shows the English pages:

Updating the tests

To be able to verify this element works like above, we need to find it in our test. We can’t find it with the getByText, getByLabelText or getByDisplayValue, so we need to add an id or a test id. Let’s add a data-testid in the PageHelmet.tsx file and use the getByTestId as it will make our test code a bit shorter:

<link rel="alternate" href={hostname + document.location.pathname} hrefLang={locale} data-testid={"alternateLink" + locale.toUpperCase()} />

Let’s update the first test in App.test.tsx to check if the alternate link renders properly:

test('Verify home page content', async () => {
const {container} = render(<App/>);
const pageHeaderContent = container.querySelector('#pageHeader')?.firstChild?.textContent;
expect(pageHeaderContent).toMatch('Home page');

await waitForDomChange();
expect(document.title).toMatch('Home page');

const descriptionMetaTag = within(document.head).getByTestId('metaDescription') as any as HTMLMetaElement;
expect(descriptionMetaTag.content).toMatch('This is the home page');

const alternateLinkNL = within(document.head).getByTestId('alternateLinkNL') as any as HTMLLinkElement;


See the full test here.

This was it so far. I will write another post on how to add canonical links to prevent duplicate indexing.




Java/TypeScript Developer. Interested in web/mobile/backend/database/cloud. Freelancing, only interested in job offers from employers directly. No middle men.