Shopify Uygulamamın Next.js (React) ile oluşturulması neden bu kadar yavaş yükleniyor?

Aug 15 2020

Bu öğreticiyi takip ettim: https://shopify.dev/tutorials/build-a-shopify-app-with-node-and-react

Başından beri, uygulamamın yüklenmesi, sekmeleri değiştirirken, ngrok aracılığıyla yüklendiğinde ve localhost'ta çalıştırıldığında veya uygulama motorunda dağıtıldığında dahil olmak üzere son derece yavaştı.

Buna ne sebep olabilir?

Not: React, Next.js ve Shopify Uygulaması geliştirmede yeniyim, bu nedenle yanıt oldukça basit olabilir.

PPS: Derleme çıktısı, kırmızı renge bağlı olarak "Herkes tarafından paylaşılan İlk Yükleme JS'si" nin çok büyük olduğunu gösteriyor. Bunu nasıl araştıracağımı ve söz konusu parçaların boyutunu nasıl küçülteceğimi bilmiyorum, ancak yalnızca 214KB bu kadar yavaş yükleme sürelerini açıklayamaz, değil mi?


İnşa etmek


React Dev Tools Profiler


@ next / bundle-analyzer Çıktı:

Ayrıştırılmış

Gzip ile sıkıştırılmış


package.json

 {
      "name": "ShopifyApp1",
      "version": "1.0.0",
      "description": "",
      "main": "index.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "dev": "node server.js NODE_ENV=dev",
        "build": "next build",
        "deploy": "next build && gcloud app deploy --version=deploy",
        "start": "NODE_ENV=production node server.js",
        "analyze": "cross-env ANALYZE=true npm run build"
      },
      "keywords": [],
      "author": "",
      "license": "ISC",
      "dependencies": {
        "@google-cloud/storage": "^5.2.0",
        "@next/bundle-analyzer": "^9.5.2",
        "@sendgrid/mail": "^7.2.3",
        "@shopify/app-bridge-react": "^1.26.2",
        "@shopify/koa-shopify-auth": "^3.1.65",
        "@shopify/koa-shopify-graphql-proxy": "^4.0.1",
        "@shopify/koa-shopify-webhooks": "^2.4.3",
        "@shopify/polaris": "^5.1.0",
        "@zeit/next-css": "^1.0.1",
        "apollo-boost": "^0.4.9",
        "cors": "^2.8.5",
        "cross-env": "^7.0.2",
        "dotenv": "^8.2.0",
        "email-validator": "^2.0.4",
        "extract-domain": "^2.2.1",
        "firebase-admin": "^9.0.0",
        "graphql": "^15.3.0",
        "helmet": "^4.0.0",
        "isomorphic-fetch": "^2.2.1",
        "js-cookie": "^2.2.1",
        "koa": "^2.13.0",
        "koa-body": "^4.2.0",
        "koa-bodyparser": "^4.3.0",
        "koa-helmet": "^5.2.0",
        "koa-router": "^9.1.0",
        "koa-session": "^6.0.0",
        "next": "^9.5.1",
        "react": "^16.13.1",
        "react-apollo": "^3.1.5",
        "react-dom": "^16.13.1",
        "react-infinite-scroll-component": "^5.0.5",
        "sanitize-html": "^1.27.2",
        "scheduler": "^0.19.1",
        "store-js": "^2.0.4",
        "tldts": "^5.6.46"
      },
      "devDependencies": {
        "webpack-bundle-analyzer": "^3.8.0",
        "webpack-bundle-size-analyzer": "^3.1.0"
      },
      "browser": {
        "@google-cloud/storage": false,
        "@sendgrid/mail": false,
        "@shopify/koa-shopify-auth": false,
        "@shopify/koa-shopify-graphql-proxy": false,
        "@shopify/koa-shopify-webhooks": false,
        "cors": false,
        "email-validator": false,
        "extract-domain": false,
        "firebase-admin": false,
        "graphql": false,
        "helmet": false,
        "isomorphic-fetch": false,
        "koa": false,
        "koa-body": false,
        "koa-bodyparser": false,
        "koa-helmet": false,
        "koa-router": false,
        "koa-session": false,
        "sanitize-html": false,
        "tldts": false
      }
    }

Chrome Geliştirici Araçları Ağ Sekmesi

DÜZENLE:

npm run dev

Bazı nedenlerden dolayı, "webpack-hmr" satır yükleme süresi sürekli artmaya devam ediyor.

npm run build && npm run start


next.config.js

require("dotenv").config({path:"live.env"});
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) => {
      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;
    }
  })
);

_app.js

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("shopOrigin"), forceRedirect: true };
    return (
      <React.Fragment>
        <Head>
          <title>...</title>
          <meta charSet="utf-8" />
        </Head>
        <Provider config={config}>
          <AppProvider i18n={translations}>
            <ApolloProvider client={client}>
              <Component {...pageProps} />
            </ApolloProvider>
          </AppProvider>
        </Provider>
      </React.Fragment>
    );
  }
}

export default MyApp;

Index.js (müşteri)

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 => response.json())
    .then(result => {
      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 ? (
      <Toast content={message} error={err} onDismiss={this.handleToast}/> 
    ) : null;


    return (
      <Frame>
         <Page>
          {toastMarkup}
          <Layout>
            <Layout.AnnotatedSection
              title="..."
              description="..."
            >
              <Card sectioned>
                <Form onSubmit={this.handleSubmitEmails}>
                  <FormLayout>
                    <TextField
                      value={emails}
                      onChange={this.handleChange('emails')}
                      label="..."
                      type="emails"
                      maxlength="200"
                    />
                    <Stack distribution="trailing">
                      <Button primary submit loading={loadingEmailResponse}>
                        Save
                      </Button>
                    </Stack>
                  </FormLayout>
                </Form>
              </Card>
            </Layout.AnnotatedSection>
            <Layout.AnnotatedSection
              title="..."
              description="..."
            >
              <Card sectioned>
                <DisplayText size="small"> {domain} </DisplayText>
                <br/>
                <Form onSubmit={this.handleSubmitDomain}>
                  <FormLayout>
                    <TextField
                      value={alias}
                      onChange={this.handleChange('alias')}
                      label="..."
                      type="text"
                      maxlength="50"
                    />
                    <Stack distribution="trailing">
                      <Button primary submit loading={loadingDomainResponse}>
                        Save
                      </Button>
                    </Stack>
                  </FormLayout>
                </Form>
              </Card>
            </Layout.AnnotatedSection>
          </Layout> 
       </Page>
      </Frame>
    );
  }

  handleToast = () => {
    this.setState({
      err: false,
      message: false,
      active: false
    })
  };
  
  handleSubmitEmails = () => {
    this.setState({loadingEmailResponse:true});
    fetch(`/emails`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        emails: this.state.emails
      })
    }).then(response => response.json())
    .then(result => {
      console.log("JSON: "+JSON.stringify(result));
      if (result.err) {
        this.setState({
          err: result.err,
          message: result.err,
          active: true
        })
      }
      else {
        this.setState({message: "...", active: true});
      }
      this.setState({loadingEmailResponse:false});
    });
  };

  handleSubmitDomain = () => {
    this.setState({loadingDomainResponse:true});
    fetch(`/domain`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: 
        JSON.stringify({
          alias: this.state.alias
        })
      }).then(response => response.json())
      .then(result => {
        console.log("JSON: "+JSON.stringify(result));
        if (result.err) {
          this.setState({
            err: result.err,
            message: result.err,
            active: true
          })
        }
        else {
          this.setState({message: "...", active: true});
        }
        this.setState({loadingDomainResponse:false});
      });
  };

  


  handleChange = (field) => {
    return (value) => this.setState({ [field]: value });
  };
}

export default Index;

server.js

const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
app.prepare().then(() => {
    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) => {
        let domain = ctx.session.shop;
        let alias;
        const snap = await global.db.collection("...").doc(ctx.session.shop).get();
            if (snap.data().alias) {
                alias = snap.data().alias;
            }
            let emails = snap.data().emails;
            let emailString = "";
            if (!emails) {
                ctx.response.body = {err: "..."};
            }
            else if(emails.length < 4) {
                for (email of emails) {
                    emailString += (","+email);
                }
                theEmailString = emailString.substring(1);
                let response = {
                    domain: domain,
                    alias: alias,
                    emails: theEmailString
                }
                ctx.response.body = response;
            } 
            else {
                ctx.response.body = {err: "..."};
            }
    });
});

Düzenle

İlk cevabı verdim, ancak mümkünse daha iyisini arıyorum.

Ayrıca, Shopify uygulaması köprü gezinme bağlantılarının tam sayfa yeniden yüklemelerini tetiklemek yerine next.js yönlendiricisini kullanmasını sağlamak da mümkün görünüyor:

https://shopify.dev/tools/app-bridge/actions/navigation

Birisi next.js için bunun nasıl yapılacağını yeterli ayrıntıyla paylaşırsa, bu benim cevabımdan daha iyi olur.

Yanıtlar

Xandor Aug 15 2020 at 23:48

Dizindeki ilk yükünüz, geliştirme araçları şelalenize göre, yalnızca 18,5 KB veri için neredeyse 2 saniye sürdü. Bu endişe verici derecede yavaştır ve kaynaklarınızın geri kalanına bile ulaşmadan önce. İlk düşüncem ağ / sunucu gecikmesi olurdu. Bunu yerel olarak mı yoksa bir tür web sunucusunda mı barındırıyorsunuz?

Olabildiğince soyardım, hatta belki sadece bir başlık içeren basit bir index.html dosyasını deneyip yüklemeyi deneyebilirim. Bunun yüklenmesi birkaç saniye sürerse, yükseltmeniz veya daha iyi bir ana bilgisayara geçmeniz gerekebilir. Yerel olarak barındırıyorsanız, bu sadece internetinizin düşük yükleme hızına sahip olmasının bir sorunu olabilir. Çoğu internet planının hızlı indirmeleri vardır, ancak yavaş yüklemeler vardır ve her zaman ISS'nizin vaat ettiklerini alamazsınız.

LandryPlacid Aug 16 2020 at 20:11

Gereksiz herhangi bir kodu kaldırarak kodunuzu optimize etmeyi deneyin. Daha dinamik içe aktarmayı kullanarak, daha hızlı bir başlangıç ​​bioler plaka yüklemesi ve çizelgeler, grafikler ve resimler gibi ağır kodlar ve müşteri sonunda daha sonraki bir zamanda video yüklemesi elde etmeye çalışın. "sonraki / dinamik" öğesinden dinamik içe aktarın, bu, müşteriye youtube gibi hızlı bir ilk boyama görünümü vermelidir.

https://nextjs.org/docs/advanced-features/dynamic-import

react Formik'i (Küçük uygulamalar için optimize bir form denetleyicisi) deneyin ve ayrıca Sınıf Bileşenleri yerine işlev bileşenlerini kullanmayı deneyin. Next'i kullanarak getStatiProps, getServerSideProps, getStaticPaths'te veritabanı çağrılarınızın çoğunu yapabilirsiniz. periyodik önbelleğe alınmış getirme için SWR kancasını kullanın.

TheProgrammer Aug 22 2020 at 21:37

Sorumda npm run dev kullanarak ölçülen yükleme sürelerini paylaştım, ancak burada prod modundaki yükleme süreleri hakkında da bazı bilgiler var.

Shopify yönetici kullanıcı arayüzüne yerleştirmeyi kaldırdıktan sonra uygulamayı üretim modunda ( npm run buildve npm run start) çalıştırmak, uygulamanın üretim modunda yüklenmesinin toplamda yaklaşık 2 saniye sürdüğünü gösteriyor ki bu hala çok yavaş görünüyor (Shopify UI yaklaşık 3 saniye ekliyordu).

Shopify uygulamasındaki gelin gezinme bağlantıları, Next.js bağlantıları gibi sayfaları değiştirmek yerine tıklandığında tam sayfa yeniden yükleme yapar.

Uygulamada Gezinme bağlantıları Sonraki bağlantılarla değiştirildi.

Yine de ilk yükleme için 1,86 saniye çok yavaş ve daha iyi çözümlere açığım.