title: Why is my Shopify App built with Next.js (React) so slow to load?
body: <p>I followed this tutorial: <a href="https://shopify.dev/tutorials/build-a-shopify-app-with-node-and-react" rel="noreferrer">https://shopify.dev/tutorials/build-a-shopify-app-with-node-and-react</a></p>
  <p>From the beginning, my app was extremely slow to load, including when changing tabs, including when loaded through ngrok and run on localhost or deployed on app engine.</p>
  <p>What could be causing this ?</p>
  <p>P.S.: I am new to React, Next.js and Shopify App development, so the answer could be quite basic.</p>
  <p>P.P.S.: The build output seems to indicate &quot;First Load JS shared by all&quot; is too large based on the red color. I do not know how to investigate this and reduce the size of said chunks although a mere 214KB could not explain such slow load times, could it ?</p>
  <hr />
  <p><strong>Build</strong></p>
  <p><a href="https://i.stack.imgur.com/GehRz.png" rel="noreferrer"><img src="https://i.stack.imgur.com/GehRz.png" alt="enter image description here" /></a></p>
  <hr />
  <p><strong>React Dev Tools Profiler</strong></p>
  <p><a href="https://i.stack.imgur.com/uEVQs.png" rel="noreferrer"><img src="https://i.stack.imgur.com/uEVQs.png" alt="enter image description here" /></a></p>
  <p><a href="https://i.stack.imgur.com/Nw366.png" rel="noreferrer"><img src="https://i.stack.imgur.com/Nw366.png" alt="enter image description here" /></a></p>
  <hr />
  <p><strong>@next/bundle-analyzer Output:</strong></p>
  <p>Parsed</p>
  <p><a href="https://i.stack.imgur.com/PyxZ7.jpg" rel="noreferrer"><img src="https://i.stack.imgur.com/PyxZ7.jpg" alt="enter image description here" /></a></p>
  <p><a href="https://i.stack.imgur.com/xrHX7.jpg" rel="noreferrer"><img src="https://i.stack.imgur.com/xrHX7.jpg" alt="enter image description here" /></a></p>
  <p>Gzipped</p>
  <p><a href="https://i.stack.imgur.com/7CD7F.jpg" rel="noreferrer"><img src="https://i.stack.imgur.com/7CD7F.jpg" alt="enter image description here" /></a></p>
  <p><a href="https://i.stack.imgur.com/bUYPZ.jpg" rel="noreferrer"><img src="https://i.stack.imgur.com/bUYPZ.jpg" alt="enter image description here" /></a></p>
  <hr />
  <p><strong>package.json</strong></p>
  <pre><code> {
        &quot;name&quot;: &quot;ShopifyApp1&quot;,
        &quot;version&quot;: &quot;1.0.0&quot;,
        &quot;description&quot;: &quot;&quot;,
        &quot;main&quot;: &quot;index.js&quot;,
        &quot;scripts&quot;: {
          &quot;test&quot;: &quot;echo \&quot;Error: no test specified\&quot; &amp;&amp; exit 1&quot;,
          &quot;dev&quot;: &quot;node server.js NODE_ENV=dev&quot;,
          &quot;build&quot;: &quot;next build&quot;,
          &quot;deploy&quot;: &quot;next build &amp;&amp; gcloud app deploy --version=deploy&quot;,
          &quot;start&quot;: &quot;NODE_ENV=production node server.js&quot;,
          &quot;analyze&quot;: &quot;cross-env ANALYZE=true npm run build&quot;
        },
        &quot;keywords&quot;: [],
        &quot;author&quot;: &quot;&quot;,
        &quot;license&quot;: &quot;ISC&quot;,
        &quot;dependencies&quot;: {
          &quot;@google-cloud/storage&quot;: &quot;^5.2.0&quot;,
          &quot;@next/bundle-analyzer&quot;: &quot;^9.5.2&quot;,
          &quot;@sendgrid/mail&quot;: &quot;^7.2.3&quot;,
          &quot;@shopify/app-bridge-react&quot;: &quot;^1.26.2&quot;,
          &quot;@shopify/koa-shopify-auth&quot;: &quot;^3.1.65&quot;,
          &quot;@shopify/koa-shopify-graphql-proxy&quot;: &quot;^4.0.1&quot;,
          &quot;@shopify/koa-shopify-webhooks&quot;: &quot;^2.4.3&quot;,
          &quot;@shopify/polaris&quot;: &quot;^5.1.0&quot;,
          &quot;@zeit/next-css&quot;: &quot;^1.0.1&quot;,
          &quot;apollo-boost&quot;: &quot;^0.4.9&quot;,
          &quot;cors&quot;: &quot;^2.8.5&quot;,
          &quot;cross-env&quot;: &quot;^7.0.2&quot;,
          &quot;dotenv&quot;: &quot;^8.2.0&quot;,
          &quot;email-validator&quot;: &quot;^2.0.4&quot;,
          &quot;extract-domain&quot;: &quot;^2.2.1&quot;,
          &quot;firebase-admin&quot;: &quot;^9.0.0&quot;,
          &quot;graphql&quot;: &quot;^15.3.0&quot;,
          &quot;helmet&quot;: &quot;^4.0.0&quot;,
          &quot;isomorphic-fetch&quot;: &quot;^2.2.1&quot;,
          &quot;js-cookie&quot;: &quot;^2.2.1&quot;,
          &quot;koa&quot;: &quot;^2.13.0&quot;,
          &quot;koa-body&quot;: &quot;^4.2.0&quot;,
          &quot;koa-bodyparser&quot;: &quot;^4.3.0&quot;,
          &quot;koa-helmet&quot;: &quot;^5.2.0&quot;,
          &quot;koa-router&quot;: &quot;^9.1.0&quot;,
          &quot;koa-session&quot;: &quot;^6.0.0&quot;,
          &quot;next&quot;: &quot;^9.5.1&quot;,
          &quot;react&quot;: &quot;^16.13.1&quot;,
          &quot;react-apollo&quot;: &quot;^3.1.5&quot;,
          &quot;react-dom&quot;: &quot;^16.13.1&quot;,
          &quot;react-infinite-scroll-component&quot;: &quot;^5.0.5&quot;,
          &quot;sanitize-html&quot;: &quot;^1.27.2&quot;,
          &quot;scheduler&quot;: &quot;^0.19.1&quot;,
          &quot;store-js&quot;: &quot;^2.0.4&quot;,
          &quot;tldts&quot;: &quot;^5.6.46&quot;
        },
        &quot;devDependencies&quot;: {
          &quot;webpack-bundle-analyzer&quot;: &quot;^3.8.0&quot;,
          &quot;webpack-bundle-size-analyzer&quot;: &quot;^3.1.0&quot;
        },
        &quot;browser&quot;: {
          &quot;@google-cloud/storage&quot;: false,
          &quot;@sendgrid/mail&quot;: false,
          &quot;@shopify/koa-shopify-auth&quot;: false,
          &quot;@shopify/koa-shopify-graphql-proxy&quot;: false,
          &quot;@shopify/koa-shopify-webhooks&quot;: false,
          &quot;cors&quot;: false,
          &quot;email-validator&quot;: false,
          &quot;extract-domain&quot;: false,
          &quot;firebase-admin&quot;: false,
          &quot;graphql&quot;: false,
          &quot;helmet&quot;: false,
          &quot;isomorphic-fetch&quot;: false,
          &quot;koa&quot;: false,
          &quot;koa-body&quot;: false,
          &quot;koa-bodyparser&quot;: false,
          &quot;koa-helmet&quot;: false,
          &quot;koa-router&quot;: false,
          &quot;koa-session&quot;: false,
          &quot;sanitize-html&quot;: false,
          &quot;tldts&quot;: false
        }
      }
  </code></pre>
  <hr />
  <p><strong>Chrome Dev Tools Network Tab</strong></p>
  <p>EDIT:</p>
  <pre><code>npm run dev
  </code></pre>
  <p><a href="https://i.stack.imgur.com/4XBDH.png" rel="noreferrer"><img src="https://i.stack.imgur.com/4XBDH.png" alt="enter image description here" /></a></p>
  <p>For some reason, the &quot;webpack-hmr&quot; line load time keeps constantly increasing.</p>
  <pre><code>npm run build &amp;&amp; npm run start
  </code></pre>
  <p><a href="https://i.stack.imgur.com/I5WjN.png" rel="noreferrer"><img src="https://i.stack.imgur.com/I5WjN.png" alt="enter image description here" /></a></p>
  <hr />
  <p><strong>next.config.js</strong></p>
  <pre><code>require(&quot;dotenv&quot;).config({path:&quot;live.env&quot;});
  const withCSS = require('@zeit/next-css');
  const webpack = require('webpack');
  const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
  const withBundleAnalyzer = require('@next/bundle-analyzer')({enabled: process.env.ANALYZE === 'true'})
  const apiKey =  JSON.stringify(process.env.SHOPIFY_API_KEY);
  module.exports =  withBundleAnalyzer(
    withCSS({
      distDir: 'build',
      webpack: (config) =&gt; {
        const env = { API_KEY: apiKey };
        config.plugins.push(new webpack.DefinePlugin(env));
        config.plugins.push(new webpack.DefinePlugin(new BundleAnalyzerPlugin()));
        config.resolve = {
          alias: {
            'react-dom$': 'react-dom/profiling',
            'scheduler/tracing': 'scheduler/tracing-profiling'
          },
          ...config.resolve
        };
        return config;
      }
    })
  );
  </code></pre>
  <hr />
  <p><strong>_app.js</strong></p>
  <pre><code>import App from 'next/app';
  import Head from 'next/head';
  import { AppProvider } from '@shopify/polaris';
  import { Provider } from '@shopify/app-bridge-react';
  import '@shopify/polaris/dist/styles.css'
  import translations from '@shopify/polaris/locales/en.json';
  import Cookies from 'js-cookie';
  import ApolloClient from 'apollo-boost';
  import { ApolloProvider } from 'react-apollo';
  
  const client = new ApolloClient({
    fetchOptions: {
      credentials: 'include'
    },
  });
  
  class MyApp extends App {
    render() {
      const { Component, pageProps } = this.props;
      const config = { apiKey: API_KEY, shopOrigin: Cookies.get(&quot;shopOrigin&quot;), forceRedirect: true };
      return (
        &lt;React.Fragment&gt;
          &lt;Head&gt;
            &lt;title&gt;...&lt;/title&gt;
            &lt;meta charSet=&quot;utf-8&quot; /&gt;
          &lt;/Head&gt;
          &lt;Provider config={config}&gt;
            &lt;AppProvider i18n={translations}&gt;
              &lt;ApolloProvider client={client}&gt;
                &lt;Component {...pageProps} /&gt;
              &lt;/ApolloProvider&gt;
            &lt;/AppProvider&gt;
          &lt;/Provider&gt;
        &lt;/React.Fragment&gt;
      );
    }
  }
  
  export default MyApp;
  </code></pre>
  <hr />
  <p><strong>Index.js (client)</strong></p>
  <pre><code>import {
    Button,
    Card,
    Form,
    FormLayout,
    Layout,
    Page,
    Stack,
    TextField,
    DisplayText,
    Toast,
    Frame
  } from '@shopify/polaris';
  
  class Index extends React.Component  {
  
    state = {
      emails: '',
      domain: '' ,
      alias: '',
      err: '',
      message: '',
      active: false,
      loadingDomainResponse: false,
      loadingEmailResponse: false
    };
  
    componentDidMount() {
      fetch(`/state`, {
        method: 'GET'
      }).then(response =&gt; response.json())
      .then(result =&gt; {
        if (result.err) {
          this.setState({
            err: result.err,
            message: result.err,
            active: true
          })
        }
        else {
          this.setState({
            emails: result.emails,
            domain: result.domain,
            alias: result.alias
          })
        }
      });
    };
  
    
  
    render() {
  
      const { emails, domain, alias, err, message, active, loadingEmailResponse, loadingDomainResponse} = this.state;
      
  
      const toastMarkup = active ? (
        &lt;Toast content={message} error={err} onDismiss={this.handleToast}/&gt; 
      ) : null;
  
  
      return (
        &lt;Frame&gt;
           &lt;Page&gt;
            {toastMarkup}
            &lt;Layout&gt;
              &lt;Layout.AnnotatedSection
                title=&quot;...&quot;
                description=&quot;...&quot;
              &gt;
                &lt;Card sectioned&gt;
                  &lt;Form onSubmit={this.handleSubmitEmails}&gt;
                    &lt;FormLayout&gt;
                      &lt;TextField
                        value={emails}
                        onChange={this.handleChange('emails')}
                        label=&quot;...&quot;
                        type=&quot;emails&quot;
                        maxlength=&quot;200&quot;
                      /&gt;
                      &lt;Stack distribution=&quot;trailing&quot;&gt;
                        &lt;Button primary submit loading={loadingEmailResponse}&gt;
                          Save
                        &lt;/Button&gt;
                      &lt;/Stack&gt;
                    &lt;/FormLayout&gt;
                  &lt;/Form&gt;
                &lt;/Card&gt;
              &lt;/Layout.AnnotatedSection&gt;
              &lt;Layout.AnnotatedSection
                title=&quot;...&quot;
                description=&quot;...&quot;
              &gt;
                &lt;Card sectioned&gt;
                  &lt;DisplayText size=&quot;small&quot;&gt; {domain} &lt;/DisplayText&gt;
                  &lt;br/&gt;
                  &lt;Form onSubmit={this.handleSubmitDomain}&gt;
                    &lt;FormLayout&gt;
                      &lt;TextField
                        value={alias}
                        onChange={this.handleChange('alias')}
                        label=&quot;...&quot;
                        type=&quot;text&quot;
                        maxlength=&quot;50&quot;
                      /&gt;
                      &lt;Stack distribution=&quot;trailing&quot;&gt;
                        &lt;Button primary submit loading={loadingDomainResponse}&gt;
                          Save
                        &lt;/Button&gt;
                      &lt;/Stack&gt;
                    &lt;/FormLayout&gt;
                  &lt;/Form&gt;
                &lt;/Card&gt;
              &lt;/Layout.AnnotatedSection&gt;
            &lt;/Layout&gt; 
         &lt;/Page&gt;
        &lt;/Frame&gt;
      );
    }
  
    handleToast = () =&gt; {
      this.setState({
        err: false,
        message: false,
        active: false
      })
    };
    
    handleSubmitEmails = () =&gt; {
      this.setState({loadingEmailResponse:true});
      fetch(`/emails`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          emails: this.state.emails
        })
      }).then(response =&gt; response.json())
      .then(result =&gt; {
        console.log(&quot;JSON: &quot;+JSON.stringify(result));
        if (result.err) {
          this.setState({
            err: result.err,
            message: result.err,
            active: true
          })
        }
        else {
          this.setState({message: &quot;...&quot;, active: true});
        }
        this.setState({loadingEmailResponse:false});
      });
    };
  
    handleSubmitDomain = () =&gt; {
      this.setState({loadingDomainResponse:true});
      fetch(`/domain`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: 
          JSON.stringify({
            alias: this.state.alias
          })
        }).then(response =&gt; response.json())
        .then(result =&gt; {
          console.log(&quot;JSON: &quot;+JSON.stringify(result));
          if (result.err) {
            this.setState({
              err: result.err,
              message: result.err,
              active: true
            })
          }
          else {
            this.setState({message: &quot;...&quot;, active: true});
          }
          this.setState({loadingDomainResponse:false});
        });
    };
  
    
  
  
    handleChange = (field) =&gt; {
      return (value) =&gt; this.setState({ [field]: value });
    };
  }
  
  export default Index;
  </code></pre>
  <hr />
  <p><strong>server.js</strong></p>
  <pre><code>const dev = process.env.NODE_ENV !== 'production';
  const app = next({ dev });
  app.prepare().then(() =&gt; {
      const server = new Koa();
      const router = new Router();
      server.use(bodyParser());
      server.use(session({ secure: true, sameSite: 'none' }, server));
      server.keys = [SHOPIFY_API_SECRET_KEY];
      router.get('/state',  async (ctx) =&gt; {
          let domain = ctx.session.shop;
          let alias;
          const snap = await global.db.collection(&quot;...&quot;).doc(ctx.session.shop).get();
              if (snap.data().alias) {
                  alias = snap.data().alias;
              }
              let emails = snap.data().emails;
              let emailString = &quot;&quot;;
              if (!emails) {
                  ctx.response.body = {err: &quot;...&quot;};
              }
              else if(emails.length &lt; 4) {
                  for (email of emails) {
                      emailString += (&quot;,&quot;+email);
                  }
                  theEmailString = emailString.substring(1);
                  let response = {
                      domain: domain,
                      alias: alias,
                      emails: theEmailString
                  }
                  ctx.response.body = response;
              } 
              else {
                  ctx.response.body = {err: &quot;...&quot;};
              }
      });
  });
  </code></pre>
  <h2>Edit</h2>
  <p>I have provided an initial answer, but I am looking for a better one if possible.</p>
  <p>Also, it seems possible to make the Shopify app bridge navigation links use the next.js router instead of triggering full page reloads:</p>
  <p><a href="https://shopify.dev/tools/app-bridge/actions/navigation" rel="noreferrer">https://shopify.dev/tools/app-bridge/actions/navigation</a></p>
  <p>If someone shares how to do that for next.js with sufficient detail, that would be better than my answer.</p>