Getting started with Magento PWA Studio
Published: 2020-03-11 21:04:42

---
title: Getting started with Magento PWA Studio
published: true
canonical_url: https://larsroettig.dev/getting-started-with-magento-pwa-studio
---


I want to divide this blog post into two parts. The first part is more from the business side. Is PWA the right technology for me?In the second part, I will explain how you can build your own [build your own PWA Theme for Magento 2.](#how-to-create-an-own-pwa-studio-theme)

## [<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>](#is-pwa-the-right-technology-for-my-requirements)Is PWA the right technology for my requirements

As with all software, it should start with the **why**. What can a customer or a developer expect from a PWA Studio project? In my experience, when new technology appears, stakeholders do not ask about any added business values or requirements. **What** is the advantage of using this new shining technology? PWA Studio storefronts enhance the user experience, especially if the target customer group uses smartphones or modern browsers. Google page ranking, loading speed, and offline support(to handle short interruptions) are important reasons online businesses should consider PWA storefronts.

Questions online businesses should ask:

- What are the target group of my PWA Project
- Which features I need for an MVP
  - Payments
  - Shipping methods
  - Language support

As already mentioned in my previous [blog post](https://dev.to/larsroettig/a-review-of-magento-pwa-studio-118a), it is useful to now with a small project to gather experience with the new PWA. But it would be best if you thought about what you need and what helps to earn more money or meet your business goals.

## [<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>](#how-to-create-an-own-pwa-studio-theme)How to create an own PWA Studio Theme

Basic requisites:

- Essential Experience with React, HTML, CSS, Webpack

From my point of view, there currently two ways to start with a project. Project scaffolding default supported by Magento Core Team and Fallback Studio by Jordan Eisenburger. Fallback Studio creates a wrapper around[PWA Studio](https://github.com/magento/pwa-studio)). It provides a basic fallback structure. So you can develop storefronts that depend on the venia-concept storefront. It also supports scss.

You can find the project here: [https://github.com/Jordaneisenburger/fallback-studio](https://github.com/Jordaneisenburger/fallback-studio)

### [<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>](#create-a-pwa-theme-with-scaffolding)Create a PWA Theme with Scaffolding

In this tutorial, we are going to extend the _Main_ and create our _TopBar_ component as the first element to our site.

PWA Studio comes with a Project scaffolding command, and this is a technique for auto-generating support for your project/theme. Early adopters of the PWA Studio project **forked** , and the Core Team recommends to don’t use this practice. With scaffolding, we should have the opportunity to build our own PWA Theme on top of Magento 2. The full documentation of [https://magento.github.io/pwa-studio/pwa-buildpack/scaffolding/]([https://magento.github.io/pwa-studio/pwa-buildpack/scaffolding)

**Command to create a new Project:**

```
yarn create @magento/pwa
```

**It should look like:** <picture>
        <source srcset="https://larsroettig.dev/static/d61f9cc56a74fbda66133ae6246268fd/92d98/Create_PWA.webp 250w,https://larsroettig.dev/static/d61f9cc56a74fbda66133ae6246268fd/c0ce8/Create_PWA.webp 500w,https://larsroettig.dev/static/d61f9cc56a74fbda66133ae6246268fd/bc682/Create_PWA.webp 1000w,https://larsroettig.dev/static/d61f9cc56a74fbda66133ae6246268fd/639e8/Create_PWA.webp 1500w,https://larsroettig.dev/static/d61f9cc56a74fbda66133ae6246268fd/d8f55/Create_PWA.webp 1692w" sizes="(max-width: 1000px) 100vw, 1000px" type="image/webp"></source>
        <source srcset="https://larsroettig.dev/static/d61f9cc56a74fbda66133ae6246268fd/ef2e0/Create_PWA.jpg 250w,https://larsroettig.dev/static/d61f9cc56a74fbda66133ae6246268fd/66ca5/Create_PWA.jpg 500w,https://larsroettig.dev/static/d61f9cc56a74fbda66133ae6246268fd/aaa41/Create_PWA.jpg 1000w,https://larsroettig.dev/static/d61f9cc56a74fbda66133ae6246268fd/fe044/Create_PWA.jpg 1500w,https://larsroettig.dev/static/d61f9cc56a74fbda66133ae6246268fd/4d321/Create_PWA.jpg 1692w" sizes="(max-width: 1000px) 100vw, 1000px" type="image/jpeg"></source>
        <img src="https://larsroettig.dev/static/d61f9cc56a74fbda66133ae6246268fd/aaa41/Create_PWA.jpg" alt="Create_PWA" title="Create_PWA" loading="lazy" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0">
      </picture>

#### [<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>](#overwriteextend-pwa-studio-default-component)Overwrite/Extend PWA Studio default component

**Create the library dir and copy main component:**

```
cd myproject
mkdir -p src/lib/components/
cp -R node_modules/@magento/venia-ui/lib/components/Main src/lib/components/
```

**Update Main Component:**

```
// src/lib/components/Main/main.js
import React from 'react'
import { bool, shape, string } from 'prop-types'
import { useScrollLock } from '@magento/peregrine'
import { mergeClasses } from '@magento/venia-ui/lib/classify'import Footer from '@magento/venia-ui/lib/components/Footer'import Header from '@magento/venia-ui/lib/components/Header'
import defaultClasses from './main.css'

const Main = props => {
    const { children, isMasked } = props;
    const classes = mergeClasses(defaultClasses, props.classes);

    const rootClass = isMasked ? classes.root_masked : classes.root;
    const pageClass = isMasked ? classes.page_masked : classes.page;

    useScrollLock(isMasked);

    return (
        <main className={rootClass}>
            <div>Static Block for TopBar</div>
            <Header />
            <div className={pageClass}>{children}</div>
            <Footer />
        </main>
    );
};

export default Main;

Main.propTypes = {
    classes: shape({
        page: string,
        page_masked: string,
        root: string,
        root_masked: string
    }),
    isMasked: bool
};

```

**Create a overwrite webpack plugin:**

```
// webpack.config.js

const path = require('path');
const glob = require('glob');

module.exports = class NormalModuleOverridePlugin {
  constructor(moduleOverrideMap) {
  this.name = 'NormalModuleOverridePlugin';
        this.moduleOverrideMap = moduleOverrideMap;
    }

  requireResolveIfCan(id, options = undefined) {
  try {
  return require.resolve(id, options);
        } catch (e) {
  return undefined;
        }
 }
  resolveModulePath(context, request) {
  const filePathWithoutExtension = path.resolve(context, request);
        const files = glob.sync(`${filePathWithoutExtension}@(|.*)`);
        if (files.length === 0) {
  throw new Error(`There is no file '${filePathWithoutExtension}'`);
        }
  if (files.length > 1) {
  throw new Error(
  `There is more than one file '${filePathWithoutExtension}'`
 );
        }

  return require.resolve(files[0]);
    }

  resolveModuleOverrideMap(context, map) {
  return Object.keys(map).reduce(
 (result, x) => ({
  ...result,
                [require.resolve(x)]:
                this.requireResolveIfCan(map[x]) ||
 this.resolveModulePath(context, map[x])
 }),
            {}
 );
    }

  apply(compiler) {
  if (Object.keys(this.moduleOverrideMap).length === 0) {
  return;
        }

  const moduleMap = this.resolveModuleOverrideMap(
  compiler.context,
            this.moduleOverrideMap
  );

        compiler.hooks.normalModuleFactory.tap(this.name, nmf => {
  nmf.hooks.beforeResolve.tap(this.name, resolve => {
  if (!resolve) {
  return;
                }

  const moduleToReplace = this.requireResolveIfCan(
  resolve.request,
                    { paths: [resolve.context] }
 );
                if (moduleToReplace && moduleMap[moduleToReplace]) {
  resolve.request = moduleMap[moduleToReplace];
                }

  return resolve;
            });
        });
    }
};
```

**Create overwrite mapping file:**

```
//componentOverrideMapping.js module.exports = componentOverride = {
 [`@magento/venia-ui/lib/components/Main`]: 'src/lib/components/Main' };
```

**Register the webpack plugin:**

```
// webpack.config.js
const {
  configureWebpack,
    graphQL: { getMediaURL, getUnionAndInterfaceTypes }
} = require('@magento/pwa-buildpack');
const { DefinePlugin } = require('webpack');
const HTMLWebpackPlugin = require('html-webpack-plugin');
const moduleOverridePlugin = require('./moduleOverrideWebpackPlugin');
const componentOverrideMapping = require('./componentOverrideMapping');

module.exports = async env => {
  const mediaUrl = await getMediaURL();

    global.MAGENTO_MEDIA_BACKEND_URL = mediaUrl;

    const unionAndInterfaceTypes = await getUnionAndInterfaceTypes();

    const { clientConfig, serviceWorkerConfig } = await configureWebpack({
  context: __dirname,
        vendor: [
  '@apollo/react-hooks',
            'apollo-cache-inmemory',
            'apollo-cache-persist',
            'apollo-client',
            'apollo-link-context',
            'apollo-link-http',
            'informed',
            'react',
            'react-dom',
            'react-feather',
            'react-redux',
            'react-router-dom',
            'redux',
            'redux-actions',
            'redux-thunk'
 ],
        special: {
  'react-feather': {
  esModules: true
 },
            '@magento/peregrine': {
  esModules: true,
                cssModules: true
 },
            '@magento/venia-ui': {
  cssModules: true,
                esModules: true,
                graphqlQueries: true,
                rootComponents: true,
                upward: true
 }
 },
        env
    });

    /**
* configureWebpack() returns a regular Webpack configuration object. * You can customize the build by mutating the object here, as in * this example. Since it's a regular Webpack configuration, the object * supports the `module.noParse` option in Webpack, documented here: * https://webpack.js.org/configuration/module/#modulenoparse */ clientConfig.module.noParse = [/braintree\-web\-drop\-in/];
    clientConfig.plugins = [
  ...clientConfig.plugins,
        new DefinePlugin({
  /**
* Make sure to add the same constants to * the globals object in jest.config.js. */ UNION_AND_INTERFACE_TYPES: JSON.stringify(unionAndInterfaceTypes),
            STORE_NAME: JSON.stringify('Venia')
 }),
        new HTMLWebpackPlugin({
  filename: 'index.html',
            template: './template.html',
            minify: {
  collapseWhitespace: true,
                removeComments: true
 }
 }) ];

    clientConfig.plugins.push( new moduleOverridePlugin(componentOverrideMapping) );
    return [clientConfig, serviceWorkerConfig];
};
```

**Execute Watch Command:**

```
yarn watch
```

**Result:** <picture>
        <source srcset="https://larsroettig.dev/static/4a946b9846829df590bf5f1c57e3b89d/92d98/Owrite_Test_1.webp 250w,https://larsroettig.dev/static/4a946b9846829df590bf5f1c57e3b89d/c0ce8/Owrite_Test_1.webp 500w,https://larsroettig.dev/static/4a946b9846829df590bf5f1c57e3b89d/40f15/Owrite_Test_1.webp 637w" sizes="(max-width: 637px) 100vw, 637px" type="image/webp"></source>
        <source srcset="https://larsroettig.dev/static/4a946b9846829df590bf5f1c57e3b89d/ef2e0/Owrite_Test_1.jpg 250w,https://larsroettig.dev/static/4a946b9846829df590bf5f1c57e3b89d/66ca5/Owrite_Test_1.jpg 500w,https://larsroettig.dev/static/4a946b9846829df590bf5f1c57e3b89d/d4ec0/Owrite_Test_1.jpg 637w" sizes="(max-width: 637px) 100vw, 637px" type="image/jpeg"></source>
        <img src="https://larsroettig.dev/static/4a946b9846829df590bf5f1c57e3b89d/d4ec0/Owrite_Test_1.jpg" alt="Owrite_Test_1" title="Owrite_Test_1" loading="lazy" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0">
      </picture>

#### [<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>](#create-your-own-pwa-component)Create your own PWA Component

```
// src/lib/components/TopBar/topbar.css
.root
{
  background-color: rgb(var(--venia-teal));
  padding: 0.5rem;
  color: #fff;
  display: flex;
  justify-content: center;
}

// src/lib/components/TopBar/topbar.js
import React from 'react'
import { shape, string } from 'prop-types'
import { mergeClasses } from '@magento/venia-ui/lib/classify'

import defaultClasses from './topbar.css'

const TopBar = props => {
    const classes = mergeClasses(defaultClasses, props.classes);
    return (
        <div className={classes.root}>
 Your first custom react component </div>
  )
};

TopBar.propTypes = {
    classes: shape({
        root: string,
    })
};

export default TopBar;

// src/lib/components/TopBar/index.js
export { default } from './topbar';

// src/lib/components/Main/main.js
import React from 'react'
import { bool, shape, string } from 'prop-types'
import { useScrollLock } from '@magento/peregrine'

import { mergeClasses } from '@magento/venia-ui/lib/classify'
import Footer from '@magento/venia-ui/lib/components/Footer'
import Header from '@magento/venia-ui/lib/components/Header'
import defaultClasses from './main.css'
import TopBar from "../TopBar";
const Main = props => {
    const { children, isMasked } = props;
    const classes = mergeClasses(defaultClasses, props.classes);

    const rootClass = isMasked ? classes.root_masked : classes.root;
    const pageClass = isMasked ? classes.page_masked : classes.page;

    useScrollLock(isMasked);

    return (
        <main className={rootClass}>
  <TopBar/> <Header />
  <div className={pageClass}>{children}</div>
  <Footer />
  </main>
  );
};

export default Main;

Main.propTypes = {
    classes: shape({
        page: string,
        page_masked: string,
        root: string,
        root_masked: string
    }),
    isMasked: bool
};
```

**Result:** <picture>
        <source srcset="https://larsroettig.dev/static/bf16d75fd73091b94004246916e8e74d/92d98/Owrite_Test_2.webp 250w,https://larsroettig.dev/static/bf16d75fd73091b94004246916e8e74d/c0ce8/Owrite_Test_2.webp 500w,https://larsroettig.dev/static/bf16d75fd73091b94004246916e8e74d/bc682/Owrite_Test_2.webp 1000w,https://larsroettig.dev/static/bf16d75fd73091b94004246916e8e74d/639e8/Owrite_Test_2.webp 1500w,https://larsroettig.dev/static/bf16d75fd73091b94004246916e8e74d/1ff5e/Owrite_Test_2.webp 1887w" sizes="(max-width: 1000px) 100vw, 1000px" type="image/webp"></source>
        <source srcset="https://larsroettig.dev/static/bf16d75fd73091b94004246916e8e74d/ef2e0/Owrite_Test_2.jpg 250w,https://larsroettig.dev/static/bf16d75fd73091b94004246916e8e74d/66ca5/Owrite_Test_2.jpg 500w,https://larsroettig.dev/static/bf16d75fd73091b94004246916e8e74d/aaa41/Owrite_Test_2.jpg 1000w,https://larsroettig.dev/static/bf16d75fd73091b94004246916e8e74d/fe044/Owrite_Test_2.jpg 1500w,https://larsroettig.dev/static/bf16d75fd73091b94004246916e8e74d/98790/Owrite_Test_2.jpg 1887w" sizes="(max-width: 1000px) 100vw, 1000px" type="image/jpeg"></source>
        <img src="https://larsroettig.dev/static/bf16d75fd73091b94004246916e8e74d/aaa41/Owrite_Test_2.jpg" alt="Owrite_Test_2" title="Owrite_Test_2" loading="lazy" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0">
      </picture>

### [<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>](#summary)Summary

In my opinion, starting a new PWA Studio project using the scaffolding command is the best approach for setting up a maintainable project.

## [<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>](#tips-and-tricks)Tips and Tricks

**SEO**

For JavaScript applications, it essential to use Server-Side Rendering (SSR). The benefit of using SSR isthe website content can crawl from crawlers that don’t execute JavaScript code. I recommend using tools like [prerender.io](prerender.io) or [seosnap.io](seosnap.io).When you use SSR to mind those errors, the request still can returns a 200 status code.You don’t want these to cached by Rendertron or Seosnap your need to set `<meta name="render:status_code" content="404" />` at the appropriate places.

(This article was posted to my blog at [https://larsroettig.dev](https://larsroettig.dev). You can [read it online by clicking here](https://larsroettig.dev/getting-started-with-magento-pwa-studio).)