@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 { Trans, t } from '@lingui/macro'
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.104",
5
+ "version": "9.0.0-canary.105",
6
6
  "sideEffects": true,
7
7
  "peerDependencies": {
8
- "@graphcommerce/prettier-config-pwa": "^9.0.0-canary.104"
8
+ "@graphcommerce/prettier-config-pwa": "^9.0.0-canary.105"
9
9
  },
10
10
  "prettier": "@graphcommerce/prettier-config-pwa"
11
11
  }