Menu
Menu Sheet Overlay
Search
Search Sheet

      SEO Developer Guide

      You want your users to find your Progressive Web App in search engine results, but to do that you must optimize your application for search engine crawlers. This guide is focused on the actions developers can take to increase their PWAs SEO performance using Meta tags on SPAs and built-in structured data.

      The PageMeta Component

      To learn more about useful meta tags, please refer to our recommended SEO best practices guide.

      One very basic requirement for good SEO is that all pages within a website (including PWAs) have a title tag (<title>) as well as meta tags (<meta>) with useful and descriptive text. These tags are what search engines use to build their search results with. Consider the following example of a typical search engine result might look like:

      Search engine with multiple results, each one showing a page title and descriptive text

      You can see in the screenshot above that each result has a page title and a description. These texts come from the pages’ title and meta tags, as can be seen below:

      <html>
          <head>
              <title>Merlin's Potions</title>
              <meta name="description" content="Welcome to Merlin's Potions. Camelot's number 1 online supplier of potions, books, ingredients and more." />
          </head>
          <!-- etc. -->
      </html>

      However the PWA is a single page web app, and that fact introduces challenges when it comes to controlling the content of the document’s head tag. For example, the head tag does not update by itself when navigating from page to page, because page navigations in a single page app is simulated!

      So how do we empower ourselves to take control of the head tag? Mobify employs redux actions to update the meta tags on a new page load. When generating a new page with the Mobify SDK, an actions.js file will be created for the new page. Inside the initializeTestPage function, the pageMetaDataReceived action can be dispatched with the new page’s title, description, and keywords.

      export const initializeTestPage = (id) => (dispatch, getState, {connector}) => {
          // Use the injected connector to fetch data here, eg. `connector.getProduct(id)`
          return Promise.all([
              dispatch(initializeApp()),
              dispatch(receiveTestPage({id, data: 'data'})),
              dispatch(
                  pageMetaDataReceived({
                      pageMetaData: {
                          title: 'Test Page',
                          description: 'Homepage for the Test',
                          keywords: 'test, page, SPA, PWA'
                      }
                  })
              )
          ])
      }

      It is advisable that both title text and meta description be provided on all pages (all containers). But if for some reason those are omitted, the PWA will fallback on some simple defaults. Those default values are defined in the app container using the defaultTitle and siteTitleTemplate, and are passed to an instance of react-helmet. You can customize them in the following way:

      // File:
      // web/app/containers/app/container.jsx
      const defaultTitle = 'My Site Name'
      // Thus, if no title is given then the title tag will render as "My
      // Site Name". But if a title is provided, then that will render instead

      If there is a particular format that you want your titles to follow, you can provide a title template using siteTitleTemplate (also passed to an instance of react-helmet). The %s is replaced with what ever the current title is.

      // File:
      // web/app/containers/app/container.jsx
      const siteTitleTemplate = `%s | My Site Name`
      // Thus, if the the current title is something like "Potions", the title
      // will render as: "Potions | My Site Name". If no title is provided,
      // then the default title (see above) will render instead.

      Lastly, the variables described in the above examples are passed to an instance of react-helmet in the following manner:

      // File:
      // web/app/containers/app/container.jsx
      import {Helmet} from 'react-helmet'
      
      // Somewhere in the App container's markup (usually close to the top)...
      <Helmet
          defaultTitle={defaultTitle}
          titleTemplate={siteTitleTemplate}
      />

      Structured Data

      To learn more about structured data overall, please refer to our recommended SEO best practices guide.

      According to Google, structured data is defined in the following way:

      Structured data is a standardized format for providing information about a page and classifying the page content; for example, on a recipe page, what are the ingredients, the cooking time and temperature, the calories, and so on.
      -Introduction to Structured Data

      On a practical level, structured data gives search engines the power to display much richer search results than would otherwise be possible. Instead of just a page title and description, a search engine can also display a product’s price, reviews, and more.

      Notice that the term microdata is also used throughout this document. Microdata is an HTML5 specification for structured data. In other words, microdata is one type of structured data. There are other types of structured data, but this document focuses on microdata specifically.

      A screensheet the demonstrates rich search results, which have built-in breadcrumbs, user review and price ranges

      External Documentation

      Before discussing how exactly structured data is used in a Progressive Web App, it is important to understand how the format works. The following resources are useful for learning the basics of structured data formats, such as microdata:

      Note: Any markup written to use Microdata can be tested using Google’s Structured Data testing tool

      Product Details and List Containers

      The product details and list use Schema.org’s product spec.

      Because containers are fully customizable, you are free to modify the markup as you see fit and format the microdata to your meet your exact needs using data from the Redux Store. See below for a simple example of what this might look like:

      // Basic Product Example
      render() {
          const {name, image, price, description} = this.props
      
          return (
              <div itemScope itemType="http://schema.org/Product">
                  <h1 itemProp="name">{name}</h1>
      
                  // The `itemProp` prop is required. Without it, the image will not
                  // be considered part of the product's data. However if you have
                  // other images that are unrelated to the product, then those
                  // images can exclude `itemProp`
                  <Image itemProp="image" src={image.src} alt={image.alt} />
      
                  <p itemProp="description">{description}</p>
      
                  <div itemProp="offers" itemScope itemType="http://schema.org/Offer">
                      $<span itemProp="price">{price}</span>
                      <meta itemProp="priceCurrency" content="USD" />
                  </div>
              </div>
          )
      }

      Google demonstrates more examples of structured data for products. Additionally, they describe many useful guidelines and some best practices for how products should be formatted.

      Breadcrumbs and Image Components

      The Breadcrumbs component uses the Schema.org’s BreadcrumbsList spec, and the Image component uses the Schema.org’s ImageObject spec.

      Important: note that it is required to include the includeMicroData prop for Breadcrumbs, and itemProp="image" for Image components. Without these props, their data will not be considered part of the structured data and will be ignored by crawlers. See below for a basic example:

      // Basic Product Example
      render() {
          const {name, image, price, description, breadcrumbItems} = this.props
      
          return (
              <div itemScope itemType="http://schema.org/Product">
                  <h1 itemProp="name">{name}</h1>
      
                  // Remember, the `includeMicroData` is required!
                  <Breadcrumbs
                      items={breadcrumbItems}
                      includeMicroData
                  />
      
                  // Remember that the `itemProp` prop is still required
                  // if the image is apart of the product's data.
                  <Image itemProp="image" src={image.src} alt={image.alt} />
      
                  <p itemProp="description">{description}</p>
      
                  <div itemProp="offers" itemScope itemType="http://schema.org/Offer">
                      $<span itemProp="price">{price}</span>
                      <meta itemProp="priceCurrency" content="USD" />
                  </div>
              </div>
          )
      }

      Google demonstrates more examples of structured data for breadcrumbs. It includes various use cases, guidelines, and basic best practices.

      Data Beyond Products

      While the Progressive Web App comes out of the box including some basic structured data for products, you as the developer have the freedom to expand the application with as much structured data as you need. Schema.org’s documentation has a wide variety of data structures for you to choose from, including events, persons, places, reviews, and more!

      Just don’t forget to verify your structured data using tools like Google’s structured data testing tool.

      IN THIS ARTICLE: