Tại sao Ứng dụng Shopify của tôi được xây dựng bằng Next.js (React) tải quá chậm?
Tôi đã làm theo hướng dẫn này: https://shopify.dev/tutorials/build-a-shopify-app-with-node-and-react
Ngay từ đầu, ứng dụng của tôi tải cực kỳ chậm, kể cả khi thay đổi tab, kể cả khi tải qua ngrok và chạy trên localhost hoặc triển khai trên app engine.
Điều gì có thể gây ra điều này ?
Tái bút: Tôi mới bắt đầu phát triển ứng dụng React, Next.js và Shopify, vì vậy câu trả lời có thể khá cơ bản.
PPS: Đầu ra bản dựng dường như cho biết "JS tải đầu tiên được chia sẻ bởi tất cả" quá lớn dựa trên màu đỏ. Tôi không biết làm thế nào để điều tra điều này và giảm kích thước của các khối nói trên mặc dù chỉ 214KB không thể giải thích thời gian tải chậm như vậy, có thể không?
Xây dựng
Hồ sơ công cụ React Dev
@ next / pack-analyzer Đầu ra:
Đã phân tích cú pháp
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
}
}
Tab mạng công cụ dành cho nhà phát triển của Chrome
BIÊN TẬP:
npm run dev
Vì một số lý do, thời gian tải dòng "webpack-hmr" không ngừng tăng lên.
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áy khách)
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: "..."};
}
});
});
Biên tập
Tôi đã cung cấp câu trả lời ban đầu, nhưng tôi đang tìm kiếm một câu trả lời tốt hơn nếu có thể.
Ngoài ra, có vẻ như có thể làm cho các liên kết điều hướng cầu nối ứng dụng Shopify sử dụng bộ định tuyến next.js thay vì kích hoạt tải lại toàn trang:
https://shopify.dev/tools/app-bridge/actions/navigation
Nếu ai đó chia sẻ cách làm điều đó cho next.js với đầy đủ chi tiết, điều đó sẽ tốt hơn câu trả lời của tôi.
Trả lời
Lần tải chỉ mục ban đầu của bạn, theo thác nước công cụ dành cho nhà phát triển của bạn, mất gần 2 giây với chỉ 18,5KB dữ liệu. Điều này là chậm một cách đáng báo động và trước khi phần còn lại của tài nguyên của bạn được sử dụng. Suy nghĩ đầu tiên của tôi sẽ là độ trễ mạng / máy chủ. Bạn đang lưu trữ nội bộ này hoặc trên một máy chủ web nào đó?
Tôi sẽ loại bỏ nó nhiều nhất có thể, thậm chí có thể chỉ cần thử và tải một tệp index.html đơn giản với chỉ một tiêu đề. Nếu mất vài giây để tải thì bạn có thể cần phải nâng cấp hoặc chuyển sang máy chủ lưu trữ tốt hơn. Nếu bạn đang lưu trữ cục bộ, điều này có thể chỉ là do Internet của bạn có tốc độ tải lên thấp. Nhiều gói internet có tốc độ tải xuống nhanh nhưng tải lên chậm và không phải lúc nào bạn cũng nhận được những gì ISP hứa hẹn.
thử tối ưu hóa mã của bạn bằng cách loại bỏ bất kỳ mã không cần thiết nào. Cố gắng sử dụng tính năng nhập động hơn để bạn tải nhanh tấm bioler ban đầu và các mã nặng như biểu đồ, đồ thị và hình ảnh và video tải sau đó ở cuối ứng dụng khách. nhập động từ "tiếp theo / động", điều này sẽ cung cấp cho khách hàng một chế độ xem sơn đầu tiên nhanh chóng giống như youtube.
https://nextjs.org/docs/advanced-features/dynamic-import
thử phản ứng Formik (Một bộ điều khiển biểu mẫu tối ưu hóa cho các ứng dụng nhỏ) và cũng thử sử dụng các thành phần chức năng trên Thành phần Lớp. Sử dụng Next, bạn có thể thực hiện hầu hết các lệnh gọi cơ sở dữ liệu trong getStatiProps, getServerSideProps, getStaticPaths. để tìm nạp được lưu trong bộ nhớ cache định kỳ, hãy sử dụng móc SWR.
Tôi đã chia sẻ thời gian tải được đo bằng cách sử dụng npm run dev trong câu hỏi của mình nhưng đây là một số thông tin về thời gian tải ở chế độ sản phẩm.
Chạy ứng dụng ở chế độ sản phẩm ( npm run build
và npm run start
) sau khi loại bỏ nhúng vào giao diện người dùng quản trị Shopify cho thấy rằng ứng dụng mất tổng cộng khoảng 2 giây để tải ở chế độ sản phẩm mà dường như vẫn rất chậm (Giao diện người dùng Shopify đã thêm khoảng 3 giây).
Các liên kết điều hướng cô dâu của ứng dụng Shopify sẽ tải lại toàn bộ trang khi được nhấp vào thay vì thay đổi trang như liên kết Next.js.
Đã thay thế các liên kết Điều hướng ứng dụng bằng các liên kết Tiếp theo.
Tuy nhiên, 1,86 giây cho lần tải đầu tiên là rất chậm và tôi đang tìm kiếm các giải pháp tốt hơn.