เหตุใดแอป Shopify ของฉันจึงสร้างด้วย Next.js (React) จึงโหลดช้า

Aug 15 2020

ฉันทำตามบทช่วยสอนนี้: https://shopify.dev/tutorials/build-a-shopify-app-with-node-and-react

ตั้งแต่เริ่มต้นแอปของฉันโหลดช้ามากรวมถึงเมื่อเปลี่ยนแท็บรวมถึงเมื่อโหลดผ่าน ngrok และรันบน localhost หรือติดตั้งบนแอพเอนจิน

อะไรที่ทำให้เกิดสิ่งนี้?

PS: ฉันยังใหม่กับการพัฒนา React, Next.js และ Shopify ดังนั้นคำตอบอาจค่อนข้างธรรมดา

PPS: เอาต์พุตของบิลด์ดูเหมือนจะระบุว่า "First Load JS shared by all" มีขนาดใหญ่เกินไปตามสีแดง ฉันไม่ทราบวิธีการตรวจสอบและลดขนาดของชิ้นส่วนดังกล่าวแม้ว่าเพียง 214KB ไม่สามารถอธิบายเวลาในการโหลดที่ช้าเช่นนี้ได้หรือไม่


สร้าง


ตอบสนอง Dev Tools Profiler


@ next / bundle-analyzer เอาท์พุท:

แยกวิเคราะห์

Gzipped


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 Dev Tools

แก้ไข:

npm run dev

ด้วยเหตุผลบางประการเวลาในการโหลดบรรทัด "webpack-hmr" จึงเพิ่มขึ้นอย่างต่อเนื่อง

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 (ไคลเอนต์)

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;

เซิร์ฟเวอร์ 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: "..."};
            }
    });
});

แก้ไข

ฉันได้ให้คำตอบเบื้องต้นแล้ว แต่ฉันกำลังมองหาคำตอบที่ดีกว่าถ้าเป็นไปได้

นอกจากนี้ดูเหมือนว่าจะเป็นไปได้ที่จะทำให้ลิงก์การนำทางของแอป Shopify ใช้เราเตอร์ next.js แทนการเรียกใช้การโหลดซ้ำแบบเต็มหน้า:

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

หากมีใครแบ่งปันวิธีการทำ next.js ที่มีรายละเอียดเพียงพอนั่นจะดีกว่าคำตอบของฉัน

คำตอบ

Xandor Aug 15 2020 at 23:48

การโหลดดัชนีเริ่มต้นของคุณตามน้ำตกเครื่องมือ dev ของคุณใช้เวลาเกือบ 2 วินาทีสำหรับข้อมูลเพียง 18.5KB สิ่งนี้ช้าอย่างน่าตกใจและก่อนที่ทรัพยากรที่เหลือของคุณจะถึงแม้กระทั่ง ความคิดแรกของฉันคือความล่าช้าของเครือข่าย / เซิร์ฟเวอร์ คุณโฮสต์สิ่งนี้ในพื้นที่หรือบนเว็บเซิร์ฟเวอร์บางประเภท?

ฉันจะตัดมันออกให้มากที่สุดเท่าที่จะทำได้บางทีก็แค่ลองโหลดไฟล์ index.html ง่ายๆที่มีเฉพาะส่วนหัวเท่านั้น หากใช้เวลาสองถึงสามวินาทีในการโหลดคุณอาจต้องอัปเกรดหรือย้ายไปยังโฮสต์ที่ดีกว่า หากคุณโฮสต์ในพื้นที่นี่อาจเป็นปัญหาของอินเทอร์เน็ตของคุณที่มีความเร็วในการอัปโหลดต่ำ แผนอินเทอร์เน็ตจำนวนมากมีการดาวน์โหลดที่รวดเร็ว แต่อัปโหลดช้าและคุณจะไม่ได้รับสิ่งที่ ISP ของคุณสัญญาไว้เสมอไป

LandryPlacid Aug 16 2020 at 20:11

ลองเพิ่มประสิทธิภาพโค้ดของคุณโดยลบโค้ดที่ไม่จำเป็นออก พยายามใช้การนำเข้าแบบไดนามิกมากขึ้นเพื่อที่คุณจะได้โหลดแผ่นไบโอเลอร์เริ่มต้นที่รวดเร็วและโค้ดหนัก ๆ เช่นแผนภูมิกราฟและรูปภาพและการโหลดวิดีโอในเวลาต่อมาที่ไคลเอนต์ นำเข้าไดนามิกจาก "ถัดไป / ไดนามิก" ซึ่งจะทำให้ลูกค้าได้รับมุมมองการระบายสีครั้งแรกอย่างรวดเร็วเช่นเดียวกับ youtube

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

ทดลองใช้ Formik (ตัวควบคุมฟอร์มที่ปรับให้เหมาะสมที่สุดสำหรับแอปขนาดเล็ก) และลองใช้ส่วนประกอบของฟังก์ชันเหนือส่วนประกอบของคลาส การใช้ Next คุณสามารถเรียกฐานข้อมูลส่วนใหญ่ใน getStatiProps, getServerSideProps, getStaticPaths สำหรับการดึงข้อมูลแคชเป็นระยะให้ใช้ตะขอ SWR

TheProgrammer Aug 22 2020 at 21:37

ฉันแชร์เวลาในการโหลดที่วัดโดยใช้ npm run dev ในคำถามของฉัน แต่นี่คือข้อมูลบางส่วนเกี่ยวกับเวลาในการโหลดในโหมด prod เช่นกัน

การเรียกใช้แอปในโหมด prod ( npm run buildและnpm run start) หลังจากลบการฝังลงใน UI ของผู้ดูแลระบบ Shopify แสดงให้เห็นว่าแอปใช้เวลาทั้งหมดประมาณ 2 วินาทีในการโหลดในโหมด prod ซึ่งยังดูช้ามาก (UI ของ Shopify เพิ่มเวลาประมาณ 3 วินาที)

ลิงก์การนำทางของแอป Shopify จะทำการโหลดซ้ำแบบเต็มหน้าเมื่อคลิกแทนที่จะเปลี่ยนหน้าเหมือนลิงก์ Next.js

แทนที่ลิงก์การนำทางของแอปด้วยลิงก์ถัดไป

ถึงกระนั้น 1.86 วินาทีสำหรับการโหลดครั้งแรกนั้นช้ามากและฉันพร้อมที่จะแก้ปัญหาที่ดีกว่า