Magento 2 PWA Studio SSR

2 minute read

Google’s own PWA team said serving static content is not important for SEO, because their clawlers are running JavaScript files. Then why bother with SSR (Server Side Rendering)?

It turns out it’s not so simple. The crawler comes in two waves: first, it will crawl the site without JavaScript, then days, or even weeks later, they will slowly crawl your new pages with their Chrome engine to reveal content that’s only available for browsers with JavaScript. This delay is unacceptable for most of the business cases.

There’s another reason to serve at least some static content: microbrowsers. If you share a link on Twitter (grave sin), Twitter will prefetch your link and display a preview card. This microbrowser won’t run any of the JavaScript on the page, so it will display an empty or some general text on the preview card. Not something any shop owner wants when their customers are keenly sharing their products on twitter, right?

Unfortunately, Magento’s PWA Studio does not offer any SSR solution by the time this article is written. But we can ask it nicely to do so!

Venia is using its own Apollo middleware solution, called UPWARD. UPWARD can read and understand a yml configuration that’s also capable to define graphql requests. In short, UPWARD can reach a graphql endpoint, retrieve some data, and render this data in a mustache template. And voila, the static content from a product or CMS page is there to crawl for the “dumb” crawlers.

Here is an example:

        - matches: resource.model
          pattern: '.'
          use: 200
      default: 404

      engine: mustache
      template: resource.template
        model: resource.model
        entityTypeName: resource.entityTypeName
        assets: assetManifest
        urlResolver: urlResolver
        env: env

UPWARD is interpreting the configuration in a lazy manner. It’s fetching data only when it’s required only. It can see the resource keyword and looking for it in the configuration. So we need to define it:

    - matches: urlResolver.type
      pattern: 'CMS_PAGE'
          # See the top-level 'cmsPage' object for details on its resolved data
          model: cmsPage
          name: cmsPage.title
            inline: "Page"
          template: '../venia-ui/templates/cmspage-shell.mst'

Same way, the lazy loader needs an urlResolver object as well, which is loading data from the urlResolver graphql endpoint:

urlResolverQuery: '../venia-ui/lib/queries/urlResolver.graphql'

  url: magentoGQL
  query: urlResolverQuery
      urlKey: request.url.pathname

The urlResolver graphql endpoint will return with CMS_PAGE and an id for the “home” url key. Then we can fetch the CMS page with this part of the config:


  url: magentoGQL
  query: '../venia-ui/lib/queries/getCmsPage.graphql'
    resolver: inline
      Store: request.url.country_code
      onServer: true

If you wonder why is this call being executed, scroll back a little and see the model: cmsPage in the resource: definition. Too much spaghetti for a Friday night? Well, ask JavaScript developers why the boots (JS) are on the table (server).

Then in the cmspage-shell.mst, the following will render some basic meta tags:

{{> templates/open-document}}
        <title>{{title}} - My awesome site</title>
        <meta name="title" content="{{meta_title}}">
        <meta name="keywords" content="{{meta_keywords}}">
        <meta name="description" content="{{meta_description}}">
  {{> templates/open-body }}
  {{> templates/default-initial-contents }}
  {{> templates/seed-bundles}}
{{> templates/close-document}}

This method can be used to render schema data, or even render the whole page before any JavaScript run, therefore useful to make the page available for crawlers and microbrowsers. These static pages could be cached in Varnish, such eliminating the time needed to render pages on server side.