Почему мое приложение Shopify, созданное с помощью Next.js (React), так медленно загружается?
Я последовал этому руководству: https://shopify.dev/tutorials/build-a-shopify-app-with-node-and-react
С самого начала мое приложение загружалось очень медленно, в том числе при смене вкладок, в том числе при загрузке через ngrok и запуске на локальном хосте или развертывании на движке приложения.
Что может быть причиной этого ?
PS: Я новичок в разработке приложений React, Next.js и Shopify, поэтому ответ может быть довольно простым.
PPS: Выходные данные сборки, судя по красному цвету, указывают на слишком большой размер «Первая загрузка JS, общий для всех». Я не знаю, как это исследовать и уменьшить размер указанных фрагментов, хотя всего лишь 214 КБ не может объяснить такое медленное время загрузки, не так ли?
Строить

Профилировщик React Dev Tools


@ next / bundle-analyzer Вывод:
Разобрано


Сжатый


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;
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: "..."};
}
});
});
Редактировать
Я дал первоначальный ответ, но по возможности ищу ответ получше.
Кроме того, кажется возможным заставить навигационные ссылки моста приложения Shopify использовать маршрутизатор next.js вместо запуска полной перезагрузки страницы:
https://shopify.dev/tools/app-bridge/actions/navigation
Если кто-то подробно расскажет, как это сделать для next.js, это будет лучше, чем мой ответ.
Ответы
Ваша первоначальная загрузка индекса, согласно водопаду инструментов разработчика, заняла почти 2 секунды всего для 18,5 КБ данных. Это происходит очень медленно и даже до того, как будут задействованы остальные ваши ресурсы. Моей первой мыслью было отставание сети / сервера. Вы размещаете это локально или на каком-то веб-сервере?
Я бы сократил его, насколько это возможно, возможно, даже просто попробую загрузить простой файл index.html только с заголовком. Если загрузка занимает пару секунд, возможно, вам потребуется либо обновить систему, либо перейти на более подходящий хост. Если у вас локальный хостинг, это может быть просто проблемой из-за низкой скорости загрузки в Интернете. Многие интернет-планы имеют быструю загрузку, но медленную загрузку, и вы не всегда получите то, что обещает ваш провайдер.
попробуйте оптимизировать свой код, удалив ненужный код. Постарайтесь использовать более динамический импорт, чтобы получить быструю начальную загрузку планшета биолера и тяжелые коды, такие как диаграммы, графики, изображения и видео, загружать позже на стороне клиента. import dynamic из "next / dynamic", это должно дать клиенту быстрое представление первой краски, как это делает youtube.
https://nextjs.org/docs/advanced-features/dynamic-import
попробуйте React Formik (контроллер оптимизации формы для небольших приложений), а также попробуйте использовать функциональные компоненты вместо компонентов класса. Используя Next, вы можете выполнять большинство вызовов базы данных в getStatiProps, getServerSideProps, getStaticPaths. для периодической выборки из кеша используйте ловушку SWR.

В своем вопросе я поделился временем загрузки, измеренным с помощью npm run dev, но вот некоторая информация о времени загрузки в режиме prod.
Запуск приложения в режиме prod ( npm run build
и npm run start
) после удаления встраивания в пользовательский интерфейс администратора Shopify показывает, что приложение загружается в режиме prod в общей сложности около 2 секунд, что по-прежнему кажется очень медленным (пользовательский интерфейс Shopify добавлял около 3 секунд).
Навигационные ссылки невесты приложения Shopify будут выполнять полную перезагрузку страницы при нажатии вместо изменения страниц, как это делают ссылки Next.js.
Ссылки навигации по приложениям заменены ссылками Далее.
Тем не менее, 1,86 секунды для первой загрузки - это очень медленно, и я открыт для лучших решений.