Warum wird meine mit Next.js (React) erstellte Shopify-App so langsam geladen?
Ich habe dieses Tutorial befolgt:https://shopify.dev/tutorials/build-a-shopify-app-with-node-and-react
Von Anfang an war meine App extrem langsam zu laden, auch beim Wechseln der Registerkarten, auch wenn sie über ngrok geladen und auf localhost ausgeführt oder auf der App-Engine bereitgestellt wurde.
Was könnte das verursachen?
PS: Ich bin neu in der Entwicklung von React-, Next.js- und Shopify-Apps, daher könnte die Antwort recht einfach sein.
PPS: Die Build-Ausgabe scheint anzuzeigen, dass "First Load JS shared by all" basierend auf der roten Farbe zu groß ist. Ich weiß nicht, wie ich das untersuchen und die Größe dieser Chunks reduzieren soll, obwohl nur 214 KB solche langsamen Ladezeiten nicht erklären könnten, oder?
Bauen

Reagieren Sie auf Dev Tools Profiler


@next/bundle-analyzer Ausgabe:
Geparst


Gezippt


Paket.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
}
}
Registerkarte „Netzwerk“ der Chrome-Entwicklungstools
BEARBEITEN:
npm run dev

Aus irgendeinem Grund nimmt die Ladezeit der "webpack-hmr"-Zeile ständig zu.
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 (Client)
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: "..."};
}
});
});
Bearbeiten
Ich habe eine erste Antwort gegeben, aber ich suche nach einer besseren, wenn möglich.
Außerdem scheint es möglich zu sein, die Brücken-Navigationslinks der Shopify-App dazu zu bringen, den next.js-Router zu verwenden, anstatt das Neuladen ganzer Seiten auszulösen:
https://shopify.dev/tools/app-bridge/actions/navigation
Wenn jemand mit ausreichend Details teilt, wie das für next.js geht, wäre das besser als meine Antwort.
Antworten
Das anfängliche Laden des Index dauerte laut Wasserfall Ihrer Entwicklungstools fast 2 Sekunden für nur 18,5 KB Daten. Dies ist alarmierend langsam und bevor der Rest Ihrer Ressourcen erreicht wird. Mein erster Gedanke wäre Netzwerk-/Serververzögerung. Hosten Sie dies lokal oder auf einer Art Webserver?
Ich würde es so weit wie möglich reduzieren, vielleicht sogar nur versuchen, eine einfache index.html-Datei mit nur einem Header zu laden. Wenn das Laden ein paar Sekunden dauert, müssen Sie möglicherweise entweder ein Upgrade durchführen oder auf einen besseren Host migrieren. Wenn Sie lokal hosten, könnte dies nur daran liegen, dass Ihr Internet eine niedrige Upload-Geschwindigkeit hat. Viele Internetpläne haben schnelle Downloads, aber langsame Uploads, und Sie werden nicht immer das bekommen, was Ihr ISP verspricht.
Versuchen Sie, Ihren Code zu optimieren, indem Sie unnötigen Code entfernen. Versuchen Sie, einen dynamischeren Import zu verwenden, damit Sie eine schnelle anfängliche Bioler-Plattenladung erhalten und die schweren Codes wie Diagramme, Grafiken und Bilder und Videos zu einem späteren Zeitpunkt auf der Client-Seite geladen werden. import dynamic from "next/dynamic" , dies sollte dem Client eine schnelle erste Farbansicht geben, wie dies bei YouTube der Fall ist.
https://nextjs.org/docs/advanced-features/dynamic-import
Probieren Sie React Formik aus (ein optimierter Formular-Controller für kleine Apps) und versuchen Sie auch, Funktionskomponenten über Klassenkomponenten zu verwenden. Mit Next können Sie die meisten Ihrer Datenbankaufrufe in getStatiProps, getServerSideProps, getStaticPaths ausführen. Verwenden Sie für das periodische Abrufen im Cache den SWR-Hook.

Ich habe in meiner Frage Ladezeiten geteilt, die mit npm run dev gemessen wurden, aber hier sind auch einige Informationen zu Ladezeiten im Prod-Modus.
Das Ausführen der App im Prod-Modus ( npm run build
und npm run start
) nach dem Entfernen der Einbettung in die Shopify-Admin-Benutzeroberfläche zeigt, dass das Laden der App im Prod-Modus insgesamt etwa 2 Sekunden dauert, was immer noch sehr langsam erscheint (die Shopify-Benutzeroberfläche fügte etwa 3 Sekunden hinzu).
Die Braut-Navigationslinks der Shopify-App laden ganze Seiten neu, wenn sie angeklickt werden, anstatt Seiten zu wechseln, wie dies bei Next.js-Links der Fall wäre.
Die App-Navigationslinks wurden durch Weiter-Links ersetzt.
Trotzdem sind 1,86 Sekunden für den ersten Ladevorgang sehr langsam und ich bin offen für bessere Lösungen.