@graphcommerce/docs 9.0.0-canary.104 → 9.0.0-canary.105
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## 9.0.0-canary.105
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#2427](https://github.com/graphcommerce-org/graphcommerce/pull/2427) [`d400e53`](https://github.com/graphcommerce-org/graphcommerce/commit/d400e534c89955c99a7ccb4bc8b1a0ae2ae4fbfd) - Added web vitals document ([@paales](https://github.com/paales))
|
|
8
|
+
|
|
3
9
|
## 9.0.0-canary.104
|
|
4
10
|
|
|
5
11
|
## 9.0.0-canary.103
|
|
@@ -276,7 +282,7 @@
|
|
|
276
282
|
All occurences of `<Trans>` and `t` need to be replaced:
|
|
277
283
|
|
|
278
284
|
```tsx
|
|
279
|
-
import {
|
|
285
|
+
import { t, Trans } from '@lingui/macro'
|
|
280
286
|
|
|
281
287
|
function MyComponent() {
|
|
282
288
|
const foo = 'bar'
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# Improving Core Web Vitals
|
|
2
|
+
|
|
3
|
+
GraphCommerce has a strict focus on getting great scores on Google's Core Web
|
|
4
|
+
Vitals. This document outlines the steps we take to ensure that our frontend is
|
|
5
|
+
performant.
|
|
6
|
+
|
|
7
|
+
- LCP: Good: <=2.5s Needs Improvement: <=4s Bad: >4s
|
|
8
|
+
- CLS: Good: <=0.1 Needs improvement: <=0.25 Bad: >0.25
|
|
9
|
+
- INP: Good: <=200ms Needs improvement: <=500ms Bad: >500ms
|
|
10
|
+
|
|
11
|
+
To get a good LCP and CLS there are a few things we need to do.
|
|
12
|
+
|
|
13
|
+
## Rendering phases of GraphCommerce pages
|
|
14
|
+
|
|
15
|
+
Rendering of the page is done in three main phases.
|
|
16
|
+
|
|
17
|
+
Rule of thumb: Move as much work up the phases as possible.
|
|
18
|
+
|
|
19
|
+
### Phase one: HTML/CSS and images.
|
|
20
|
+
|
|
21
|
+
The HTML document containing all the HTML and CSS is downloaded and rendered on
|
|
22
|
+
the frontend. Optimizing for this phase is usually where to start.
|
|
23
|
+
|
|
24
|
+
The goal is that the page should look great in this phase on mobile and desktop.
|
|
25
|
+
|
|
26
|
+
Of course not everything can be achieved with only HTML/CSS/Images. Customer
|
|
27
|
+
specific information is not present in this phase, the server only sends
|
|
28
|
+
session-less information to the browser. So any session specific information
|
|
29
|
+
will be missing here.
|
|
30
|
+
|
|
31
|
+
Solution: Disable JavaScript in the Chrome inspector and reload the page.
|
|
32
|
+
|
|
33
|
+
Rule of thumb: Most of the attention should be put on getting as much of the
|
|
34
|
+
page rendered as possible in this phase.
|
|
35
|
+
|
|
36
|
+
#### Images are loaded too late
|
|
37
|
+
|
|
38
|
+
Make sure the images that are required for this phase have the `loading=eager`
|
|
39
|
+
property.
|
|
40
|
+
|
|
41
|
+
### Phase two: React hydration
|
|
42
|
+
|
|
43
|
+
Hydration is the process is to "attach" React to the HTML that was rendered by
|
|
44
|
+
the server. React will attach to the HTML that exists inside the DOM and take
|
|
45
|
+
over managing the DOM inside it. Next.js automatically does this for the root
|
|
46
|
+
component.
|
|
47
|
+
|
|
48
|
+
#### Pitfall: Hydration errors
|
|
49
|
+
|
|
50
|
+
Hydration errors are problematic for performance as this will cause React to
|
|
51
|
+
rerender the entire page. This means that after hydration is complete, React
|
|
52
|
+
will start to rerender the entire page. This will thus double the amount of work
|
|
53
|
+
React has to do.
|
|
54
|
+
|
|
55
|
+
See Pitfall in in this chapter:
|
|
56
|
+
https://react.dev/reference/react-dom/client/hydrateRoot#hydrating-server-rendered-html
|
|
57
|
+
|
|
58
|
+
Solution: Keep an eye on the console for hydration errors and fix them.
|
|
59
|
+
|
|
60
|
+
#### Pitfall: Long hydration phase
|
|
61
|
+
|
|
62
|
+
Keep the hydration phase as short as possible. Web Vitals does not measure the
|
|
63
|
+
execution time directly but there is a case where this happens:
|
|
64
|
+
|
|
65
|
+
When React starts hydration it will start to listen to all event listeners. So
|
|
66
|
+
even before hydration is complete the clicks of the user will be registered and
|
|
67
|
+
will be replayed when hydration is complete. This also means that the whole
|
|
68
|
+
hydration phase will count against the INP metric.
|
|
69
|
+
|
|
70
|
+
The longer the hydration phase, the more often it will be measured as the INP
|
|
71
|
+
metric. The more negatively it will affect the Core Web Vitals and user
|
|
72
|
+
experience.
|
|
73
|
+
|
|
74
|
+
Solution: Use the React Profiler to see where work is being done and optimize
|
|
75
|
+
that.
|
|
76
|
+
|
|
77
|
+
### Phase three: Apollo Client queries
|
|
78
|
+
|
|
79
|
+
To load session specific data, Apollo Client is used. After the first render is
|
|
80
|
+
complete, the Apollo Client data becomes available.
|
|
81
|
+
|
|
82
|
+
Most frequently used queries are automatically persisted (cached) in the local
|
|
83
|
+
storage of the browser (key: `apollo-cache-persist`). By default everything is
|
|
84
|
+
persisted, except for pruned data (see
|
|
85
|
+
[persistenceMapper](https://github.com/graphcommerce-org/graphcommerce/blob/3900c7c9e3741fe110378f1a03dd54b4db8b26d9/packages/graphql/components/GraphQLProvider/persistenceMapper.ts#L28-L37)).
|
|
86
|
+
|
|
87
|
+
#### Pitfall: Non-session specific queries are used for the initial render
|
|
88
|
+
|
|
89
|
+
Since this phase is late, do not use the result of non-session specific queries
|
|
90
|
+
for the initial render. This also causes rerenders of the components causing all
|
|
91
|
+
vitals to be affected.s
|
|
92
|
+
|
|
93
|
+
Solution: Move data fetching to the `getStaticProps` or `getServerSideProps`
|
|
94
|
+
functions. That can be a bit unergonomic, in that case move the data fetching
|
|
95
|
+
requirements to the GraphQL Mesh layer, by creating additional resolvers.
|
|
96
|
+
|
|
97
|
+
For example, it has been complex to get attribute option value labels like the
|
|
98
|
+
brand of a product without doing an additional `customAttributeMetadataV2`
|
|
99
|
+
query. Fetching this information separately requires precise coordination to
|
|
100
|
+
make it work even in getStaticProps.
|
|
101
|
+
|
|
102
|
+
To solve this the query fetching the attribute option values has been moved to
|
|
103
|
+
the GraphQL Mesh layer with the
|
|
104
|
+
[`ProductInterface.custom_attributeV2`](https://github.com/graphcommerce-org/graphcommerce/blob/canary/packages/magento-graphql/schema/ProductInterface-custom_attribute.graphqls)
|
|
105
|
+
and it's
|
|
106
|
+
[resolver](https://github.com/graphcommerce-org/graphcommerce/blob/canary/packages/magento-graphql/mesh/customAttributeV2Resolver.ts)
|
|
107
|
+
and
|
|
108
|
+
[mesh configuration plugin](https://github.com/graphcommerce-org/graphcommerce/blob/canary/packages/magento-graphql/plugins/meshConfigAttrValue.ts).
|
|
109
|
+
|
|
110
|
+
## Conditionally render on mobile/desktop
|
|
111
|
+
|
|
112
|
+
TLDR: Use the `<MediaQuery>` component instead of useMediaQuery or CSS
|
|
113
|
+
breakpoints.
|
|
114
|
+
|
|
115
|
+
To render UI conditionally for various breakpoints it is practically always
|
|
116
|
+
faster to render them conditionally with CSS than it is to conditionallty render
|
|
117
|
+
components with JS. However rendering conditionally with CSS will cause
|
|
118
|
+
increased JS executing time, Total Blocking Time and possibly INP issues.
|
|
119
|
+
|
|
120
|
+
### Conditionally render with JS: useMediaQuery
|
|
121
|
+
|
|
122
|
+
useMediaQuery: When you are now using useMediaQuery to conditionally render
|
|
123
|
+
content for mobile or desktop.
|
|
124
|
+
|
|
125
|
+
This means that hooks like useMediaQuery should almost never be used.
|
|
126
|
+
[See docs](https://mui.com/material-ui/react-use-media-query/#server-side-rendering)
|
|
127
|
+
and [examples](https://mui.com/system/display/#hiding-elements).
|
|
128
|
+
|
|
129
|
+
> Server-side rendering and client-side media queries are fundamentally at odds.
|
|
130
|
+
> Be aware of the tradeoff.
|
|
131
|
+
|
|
132
|
+
Also see
|
|
133
|
+
https://mui.com/material-ui/react-use-media-query/#server-side-rendering
|
|
134
|
+
|
|
135
|
+
1. Is very slow as it has to wait for the JS to initialize on pageload.
|
|
136
|
+
2. Can cause CLS problems if the useMediaQuery is used to render elements in
|
|
137
|
+
the viewport.
|
|
138
|
+
3. Can cause LCP issues if useMediaQuery is used to render the LCP element.
|
|
139
|
+
4. Causes TBT problems as a component always needs to be rerendered. (And bad
|
|
140
|
+
TBT can cause INP problems)
|
|
141
|
+
5. HTML isn't present in the DOM, which can cause SEO issues.
|
|
142
|
+
|
|
143
|
+
### Conditionally render with CSS: CSS Media query
|
|
144
|
+
|
|
145
|
+
When you are using CSS to show or hide content based on media queries. Causes
|
|
146
|
+
TBT problems as both code paths need to be rendered. Bad TBT can cause INP
|
|
147
|
+
problems.
|
|
148
|
+
|
|
149
|
+
```tsx
|
|
150
|
+
function RenderConditionallyForCertainBreakpoints() {
|
|
151
|
+
return (
|
|
152
|
+
<>
|
|
153
|
+
<Box sx={{ display: { xs: 'block', md: 'none' } }}>
|
|
154
|
+
hide on screens wider than md
|
|
155
|
+
</Box>
|
|
156
|
+
<Box sx={{ display: { xs: 'none', md: 'block' } }}>
|
|
157
|
+
hide on screens smaller than md
|
|
158
|
+
</Box>
|
|
159
|
+
</>
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### MediaQuery component
|
|
165
|
+
|
|
166
|
+
To solve both of the above problems we can use the `<MediaQuery>` component. It
|
|
167
|
+
will conditionally render/hydrate the component based on the media query and not
|
|
168
|
+
execute the JS if the media query does not match.
|
|
169
|
+
|
|
170
|
+
1. On the server both code paths are rendered as normal, like you would with the
|
|
171
|
+
conditional render with CSS. On the first browser render (where JS is loaded)
|
|
172
|
+
it will conditionally show the component based on the CSS media query.
|
|
173
|
+
2. During hydration the component will be hydrated only if the media query
|
|
174
|
+
matches. If the media query doesn't match it will not be hydrated (and thus
|
|
175
|
+
not execute
|
|
176
|
+
the JS).
|
|
177
|
+
3. When the media query matches the component will rerender and show the
|
|
178
|
+
component.
|
|
179
|
+
4. When components are created on the client, they are conditionally rendered.
|
|
180
|
+
|
|
181
|
+
Example:
|
|
182
|
+
|
|
183
|
+
```tsx
|
|
184
|
+
import { MediaQuery } from '@graphcommerce/next-ui'
|
|
185
|
+
|
|
186
|
+
function MyLayout() {
|
|
187
|
+
return (
|
|
188
|
+
<MediaQuery query={(theme) => theme.breakpoints.up('md')}>
|
|
189
|
+
<MyExpensiveDesktopComponent>
|
|
190
|
+
Only visisble on desktop
|
|
191
|
+
</MyExpensiveDesktopComponent>
|
|
192
|
+
</MediaQuery>
|
|
193
|
+
)
|
|
194
|
+
}
|
|
195
|
+
```
|
package/package.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"name": "@graphcommerce/docs",
|
|
3
3
|
"homepage": "https://www.graphcommerce.org/docs",
|
|
4
4
|
"repository": "github:graphcommerce-org/graphcommerce/docs",
|
|
5
|
-
"version": "9.0.0-canary.
|
|
5
|
+
"version": "9.0.0-canary.105",
|
|
6
6
|
"sideEffects": true,
|
|
7
7
|
"peerDependencies": {
|
|
8
|
-
"@graphcommerce/prettier-config-pwa": "^9.0.0-canary.
|
|
8
|
+
"@graphcommerce/prettier-config-pwa": "^9.0.0-canary.105"
|
|
9
9
|
},
|
|
10
10
|
"prettier": "@graphcommerce/prettier-config-pwa"
|
|
11
11
|
}
|