Supporting multiple languages in a PWA — Part Three — Handling page titles and meta tags

Leejjon
4 min readDec 26, 2019

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

How to make page titles and metadata multilingual?

One of the challenges with modern Progressive Web Apps is SEO (Search Engine Optimization). Here’s an excelent article about what metadata tags you can use to increase your chances on being properly indexed in search engines like Google.

Our application is a “single page application” that handles the routing on the client side. You want to make sure that the Google bot is able to find pages that are loaded on the client side and index them with it’s own title and meta description tag.

React Helmet

I use the React-Helmet library to set proper titles and metadata tags per page. For this example I’ll only set the page title and add the description meta tag.

You can install it by running:

npm install react-helmet @types/react-helmet

Add the following to your App.tsx:

import React from 'react';
import {BrowserRouter, Link, Route} from 'react-router-dom';
import Helmet from 'react-helmet';
import { useTranslation } from 'react-i18next';
import Home from './views/home/Home';
import News from './views/news/News';
import About from './views/about/About';
const App: React.FC = () => {
const { t } = useTranslation();
return (
<div className="App">
<Helmet>
<title>{t('TITLE')}</title>
<meta name="description" content={t('DESCRIPTION')} />
</Helmet>
<BrowserRouter>
<Link id="linkToHome" to="/">
{t('HOME_LINK')}
</Link><br/>
<Link id="linkToNews" to="/news">
{t('NEWS_LINK')}
</Link><br/>
<Link id="linkToAbout" to="/about">
{t('ABOUT_LINK')}
</Link>
<Route exact path="/" component={Home}/>
<Route exact path="/news" component={News}/>
<Route exact path="/about" component={About}/>
</BrowserRouter>
</div>
);
};
export default App;

Now we have only defined default metatags for all pages within our clientside routing. Let’s override the meta tags for the About page in About.tsx:

import React from 'react';
import {Helmet} from "react-helmet";
import {useTranslation} from "react-i18next";
const About: React.FC = () => {
const { t } = useTranslation();
return (
<div>
<Helmet>
<title>{t('TITLE')} - {t('ABOUT_HEADER')}</title>
<meta name="description" content={t('ABOUT_CONTENT')}/>
</Helmet>
<h1 id='pageHeader'>{t('ABOUT_HEADER')}</h1>
<p>{t('ABOUT_CONTENT')}</p>
</div>
);
};
export default About;

Do the same for the Home and News page.

If you only add the title and description tags like I do, you could extract the logic to a component to avoid duplicate code. Create a component called PageHelmet:

I implemented the PageHelmet like this:

import React from "react";
import { Helmet } from "react-helmet";
type PageHelmetProps = {
pageTitle: string,
pageDescription: string
}
const PageHelmet: React.FC<PageHelmetProps> = ({ pageTitle, pageDescription}) => {
return (
<Helmet>
<title>{pageTitle}</title>
<meta name="description" content={pageDescription} data-testid="metaDescription" />
</Helmet>
);
};
export default PageHelmet;

In the Home.tsx, About.tsx and News.tsx files you can now use this PageHelmet component:

import React from 'react';
import {useTranslation} from "react-i18next";
import PageHelmet from "../../components/page-helmet/PageHelmet";
const Home: React.FC = () => {
const { t } = useTranslation();
return (
<div>
<PageHelmet pageTitle={t('HOME_HEADER')} pageDescription={t('HOME_CONTENT')} />
<h1 id="pageHeader">{t('HOME_HEADER')}</h1>
<p>{t('HOME_CONTENT')}</p>
</div>
);
};
export default Home;

Regardless whether you created a PageHelmet component like I did or use react-helmet directly, you should see the meta tags change when clicking the urls in the page.

Okay it works. Don’t forget to update the tests!

I discovered how to test whether the page title changes as expected when navigating. For some reason you need to use “waitForDomChange” before we can verify the title. I found this solution in this Github issue. Import:

import {render, fireEvent, waitForElement, waitForDomChange} from '@testing-library/react';

And add the two new lines to all tests where you want to verify the title:

test('Verify home page content', async () => {
const {container} = render(<App/>);
..... await waitForDomChange();
expect(document.title).toMatch('Home page');
});

To obtain the meta tags, we need to query on the document.head. If we want ‘within’ function to obtain elements from the document.head. If you paid attention when copying the PageHelmet.tsx file you already saw I added the data-testid="metaDescription" to the description meta tag. We can use it to find the content attribute and verify whether it contains the correct translation.

Import the within function:

import {render, fireEvent, waitForElement, waitForDomChange, within} from '@testing-library/react';

And add these two lines to verify the content of the meta description tag.

test('Verify home page content', async () => {
const {container} = render(<App/>);
.....

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

If you apply this title and meta tag assertions to every tests in the App.test.ts file will look like this.

This was it!

I hope you enjoyed this article. I would appreciate any feedback. The full source code can be found here.

--

--

Leejjon

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