@graphcommerce/docs 9.0.0-canary.99 → 9.0.0

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.
@@ -152,22 +152,33 @@ cache.
152
152
 
153
153
  The service worker caches:
154
154
 
155
- - [static fonts](https://github.com/shadowwalker/next-pwa/blob/master/cache.js#L28)
156
- - [static images](https://github.com/shadowwalker/next-pwa/blob/master/cache.js#L39)
157
- - [\_next/image](https://github.com/shadowwalker/next-pwa/blob/master/cache.js#L50)
158
- - [js](https://github.com/shadowwalker/next-pwa/blob/master/cache.js#L85)
155
+ - [static fonts](https://github.com/serwist/serwist/blob/main/packages/next/src/index.worker.ts#L27):
156
+ Google fonts and webfonts with StaleWhileRevalidate strategy
157
+ - [static images](https://github.com/serwist/serwist/blob/main/packages/next/src/index.worker.ts#L64):
158
+ jpg, jpeg, gif, png, svg, ico, webp with StaleWhileRevalidate strategy
159
+ - [\_next/image](https://github.com/graphcommerce-org/graphcommerce/blob/main/packages/service-worker/runtimeCaching.ts#L6):
160
+ Custom implementation with StaleWhileRevalidate and nextImagePlugin
161
+ - js and css files: Only for files outside of `_next/static` with
162
+ StaleWhileRevalidate strategy
163
+
164
+ Notable differences from previous implementation:
165
+
166
+ - All `_next/static` files (js, css) are excluded from runtime caching as they
167
+ are handled by the precache mechanism
168
+ - Cross-origin requests use NetworkOnly strategy instead of NetworkFirst
169
+ - Image caching has been optimized with custom configuration for better
170
+ performance
159
171
 
160
172
  Note: When a new deployment is made, the service worker is updated. This means
161
173
  that all previous caches are cleared and new caches are created.
162
174
 
163
175
  It does not cache:
164
176
 
165
- - [\_next/data](https://github.com/shadowwalker/next-pwa/blob/master/cache.js#L107):
166
- Although it looks like it does, the regex is actually wrong and it does not
167
- cache anything.
168
- - [pages](https://github.com/shadowwalker/next-pwa/blob/master/cache.js#L152)
177
+ - [\_next/data](https://github.com/ducanh-99/serwist/blob/main/packages/next/src/index.worker.ts#L137):
178
+ Uses NetworkFirst strategy to ensure fresh data
179
+ - [pages](https://github.com/ducanh-99/serwist/blob/main/packages/next/src/index.worker.ts#L152):
169
180
  Uses NetworkFirst strategy, which means it will always try to fetch the
170
- resource from the network first, and only if that fails it will use the cache.
181
+ resource from the network first, and only if that fails it will use the cache
171
182
 
172
183
  ## Cache invalidation limitations
173
184
 
@@ -157,6 +157,14 @@ When a user selects a variant, it will switch the values on the configurable pag
157
157
 
158
158
  Enabling options here will allow switching of those variants.
159
159
 
160
+ #### containerSizingContent: BREAKPOINT | FULL_WIDTH = `FULL_WIDTH`
161
+
162
+ Configures the max width of the content (main content area)
163
+
164
+ #### containerSizingShell: BREAKPOINT | FULL_WIDTH = `FULL_WIDTH`
165
+
166
+ Configures the max width of the shell (header, footer, overlays, etc.)
167
+
160
168
  #### crossSellsHideCartItems: boolean = `false`
161
169
 
162
170
  Determines if cross sell items should be shown when the user already has the product in their cart. This will result in a product will popping off the screen when you add it to the cart.
@@ -223,6 +231,10 @@ Provide a value to enable Google Analytics for your store.
223
231
 
224
232
  To override the value for a specific locale, configure in i18n config.
225
233
 
234
+ #### googlePlaystore: [GraphCommerceGooglePlaystoreConfig](#GraphCommerceGooglePlaystoreConfig)
235
+
236
+ To create an assetlinks.json file for the Android app.
237
+
226
238
  #### googleRecaptchaKey: string
227
239
 
228
240
  Google reCAPTCHA site key.
@@ -347,6 +359,10 @@ Show a message when the product is added to the wishlist.
347
359
 
348
360
  Debug configuration for GraphCommerce
349
361
 
362
+ #### cart: boolean
363
+
364
+ Enable debugging interface to debug sessions
365
+
350
366
  #### pluginStatus: boolean
351
367
 
352
368
  Reports which plugins are enabled or disabled.
@@ -370,8 +386,22 @@ Issues that this can cause are:
370
386
  - The same package is included multiple times in the bundle, increasing the bundle size.
371
387
  - The Typescript types of the package are not compatible with each other, causing Typescript errors.
372
388
 
389
+ ### GraphCommerceGooglePlaystoreConfig
390
+
391
+ See https://developer.android.com/training/app-links/verify-android-applinks#web-assoc
392
+
393
+ #### packageName: string (required)
394
+
395
+ The package name of the Android app.
396
+
397
+ #### sha256CertificateFingerprint: string (required)
398
+
399
+ The sha256 certificate fingerprint of the Android app.
400
+
373
401
  ### GraphCommercePermissions
374
402
 
403
+ Permissions input
404
+
375
405
  #### cart: CUSTOMER_ONLY | DISABLED | ENABLED
376
406
 
377
407
  Changes the availability of the add to cart buttons and the cart page to either customer only or completely disables it.
@@ -386,6 +416,8 @@ Enables / disabled the account section of the website. DISABLE_REGISTRATION will
386
416
 
387
417
  #### website: ENABLED
388
418
 
419
+ Allows the option to require login or completely disable the site.
420
+
389
421
  ### GraphCommerceStorefrontConfig
390
422
 
391
423
  All storefront configuration for the project
package/framework/seo.md CHANGED
@@ -4,6 +4,30 @@ menu: SEO
4
4
 
5
5
  # SEO
6
6
 
7
+ We optimize for Search engines by default by integrating a few key features:
8
+
9
+ ## Canonical URL
10
+
11
+ We provde a
12
+ [canonicalize / useCanonical](https://github.com/graphcommerce-org/graphcommerce/blob/canary/packages/next-ui/PageMeta/canonicalize.ts)
13
+ function that can be used to generate canonical URLs based on the current router
14
+ and further configuration. This functionaliy is used by the PageMeta component
15
+ as well as robots.txt and sitemap generation.
16
+
17
+ It considers the current locale, the domain.
18
+
19
+ ## JsonLd
20
+
21
+ We integrated [JSON-LD](https://json-ld.org/) for pages.
22
+
23
+ A
24
+ [`<JsonLd>`](https://github.com/graphcommerce-org/graphcommerce/blob/main/packages/next-ui/JsonLd/JsonLd.tsx)
25
+ component provides Json-LD metadata for your pages. See
26
+ [<ProductPageJsonLd>](https://github.com/graphcommerce-org/graphcommerce/blob/main/packages/magento-product/components/JsonLdProduct/ProductPageJsonLd.tsx)
27
+ or
28
+ [`<BreadcrumbJsonLd>`](https://github.com/graphcommerce-org/graphcommerce/blob/main/packages/next-ui/BreadcrumbJsonLd/BreadcrumbJsonLd.tsx)
29
+ for examples.
30
+
7
31
  ## Page Meta data
8
32
 
9
33
  Page meta data is handled by the
@@ -13,7 +37,6 @@ are hardcoded).
13
37
 
14
38
  ```tsx
15
39
  // Example from /cart.tsx
16
-
17
40
  <PageMeta
18
41
  title={t`Cart (${data?.cart?.total_quantity ?? 0})`}
19
42
  metaDescription={t`Cart Items`}
@@ -26,7 +49,6 @@ Dynamic example
26
49
 
27
50
  ```tsx
28
51
  // Example from /product/[url].tsx
29
-
30
52
  <PageMeta
31
53
  title={page?.metaTitle ?? title ?? ''}
32
54
  metaDescription={page?.metaDescription ?? ''}
@@ -35,7 +57,13 @@ Dynamic example
35
57
  />
36
58
  ```
37
59
 
38
- ## Robots.txt & XML sitemaps
60
+ ## Robots.txt
61
+
62
+ Robots.txt and the sitemap files are generated on the fly and do not rely on any
63
+ static generation.
64
+
65
+ Robots.txt is generated during runtime so that a separate robots.txt can be
66
+ served for each domain that is configured.
39
67
 
40
68
  GraphCommerce serves robots.txt & XML sitemap routes through the page router.
41
69
  (`pages/robots.txt.tsx` & `pages/sitemap/`)
@@ -46,7 +74,31 @@ separate sitemap for product, category & content pages.
46
74
  ### Multi domain setup
47
75
 
48
76
  When using a multi domain setup (e.g. https://mydomain.nl &
49
- https://mydomain.com) using the
77
+ https://mydomain.com) configure the
78
+ [`domain` configuration](./config.md#domain-string) in the storefront config.
79
+
80
+ ```js
81
+ /** @type {import('@graphcommerce/next-config/src/generated/config').GraphCommerceConfig} */
82
+ const config = {
83
+ // ...
84
+ storefront: [
85
+ {
86
+ locale: 'en',
87
+ magentoStoreCode: 'en_US',
88
+ defaultLocale: true,
89
+ domain: 'https://mydomain.com',
90
+ },
91
+ {
92
+ locale: 'nl',
93
+ magentoStoreCode: 'nl_NL',
94
+ defaultLocale: true,
95
+ domain: 'https://mydomain.nl',
96
+ },
97
+ // ...
98
+ ],
99
+ }
100
+ ```
101
+
50
102
  [`canonicalBaseUrl` configuration](./config.md#canonicalbaseurl-string), the
51
103
  robots.txt will only include sitemaps specific to that domain.
52
104
 
@@ -54,9 +106,20 @@ robots.txt will only include sitemaps specific to that domain.
54
106
 
55
107
  When using a multi locale based setup (e.g.
56
108
  https://graphcommerce.vercel.app/en-gb), the robots.txt will include sitemaps
57
- for all locales on the global domain. Example:
109
+ for all locales the configured domain. Example:
58
110
  [robots.txt](https://graphcommerce.vercel.app/robots.txt)
59
111
 
112
+ ## Sitemaps
113
+
114
+ GraphCommerce generates sitemaps per locale for
115
+ [sitemap/product.xml](https://github.com/graphcommerce-org/graphcommerce/blob/canary/examples/magento-graphcms/pages/sitemap/product.xml.tsx),
116
+ [sitemap/category.xml](https://github.com/graphcommerce-org/graphcommerce/blob/canary/examples/magento-graphcms/pages/sitemap/category.xml.tsx)
117
+ and
118
+ [sitemap/content.xml](https://github.com/graphcommerce-org/graphcommerce/blob/canary/examples/magento-graphcms/pages/sitemap/content.xml.tsx).
119
+
120
+ Limitations: We currently do not support hreflang in sitemaps since there is no
121
+ Magento GraphQL API to get the hreflang for a page.
122
+
60
123
  ## Next steps
61
124
 
62
125
  - Learn about [Static Generation (SSG)](../framework/static-generation.md) in
@@ -8,10 +8,10 @@ metaTitle: Getting started with GraphCommerce
8
8
  In this guide, you will set up a GraphCommerce app locally, allowing you to
9
9
  start building.
10
10
 
11
- ### Requirements
11
+ ### Preparations
12
12
 
13
- - Lixux, MacOS or WSL2
14
- - Install and use node 16/18: `nvm install 16` or `nvm use 16`
13
+ - MacOS, Windows with WSL2 or Linux
14
+ - Install and use node 20: `nvm install 20` or `nvm use 20`
15
15
  - Install yarn: `corepack enable`
16
16
 
17
17
  ## Step 1: Create a GraphCommerce app
@@ -26,19 +26,34 @@ mkdir my-project
26
26
  # Create project folder
27
27
  ```
28
28
 
29
+ There are a few starting points to choose from with or without Hygraph:
30
+
31
+ Option 1: Only Magento Open Source:
32
+
29
33
  ```bash
30
- cp -R graphcommerce/examples/magento-graphcms/. my-project && rm -rf graphcommerce && cd my-project
31
- # Copy example, delete repo, navigate to project folder
34
+ cp -R graphcommerce/examples/magento-magento-open-source/. my-project && cd my-project
32
35
  ```
33
36
 
37
+ Option 2: Magento Open Source + Hygraph:
38
+
39
+ ```bash
40
+ cp -R graphcommerce/examples/magento-graphcms/. my-project && cd my-project
41
+ ```
42
+
43
+ Option 3: Adobe Commerce:
44
+
45
+ Please contact us for more information to get access to the Adobe Commerce
46
+ starting point.
47
+
34
48
  ## Step 2: Configure API keys (optional)
35
49
 
36
50
  Duplicate and rename the configuration example file to:
37
51
  `graphcommerce.config.js` and configure the following:
38
52
 
39
- - `magentoEndpoint` [?](../framework/config.md#magentoendpoint-string)
40
- - `hygraphEndpoint` [?](../framework/config.md#hygraphendpoint-string)
41
- - `magentoStoreCode` [?](../framework/config.md#magentostorecode-string)
53
+ - `magentoEndpoint` [?](../framework/config.md#magentoendpoint-string-required)
54
+ - `hygraphEndpoint` [?](../framework/config.md#hygraphendpoint-string-required)
55
+ - `magentoStoreCode`
56
+ [?](../framework/config.md#magentostorecode-string-required)
42
57
 
43
58
  > magentoStoreCode
44
59
  >
@@ -55,11 +70,12 @@ Duplicate and rename the configuration example file to:
55
70
 
56
71
  ### Requirements
57
72
 
58
- - Magento version 2.4.3 or higher - Clean install, a production or a development
59
- environment
73
+ - Magento version 2.4.5 or higher - Clean install, a production or a development
74
+ environment (technically 2.4.3 and 2.4.4 also work, but in practice important
75
+ bugfixes have been made in the latest versions.)
60
76
  - Hygraph - A project with the required schema.
61
77
  [Clone ↗](https://app.hygraph.com/clone/caddaa93cfa9436a9e76ae9c0f34d257?name=GraphCommerce%20Demo)
62
- the schema as your starting point.
78
+ the schema as your starwting point.
63
79
 
64
80
  ## Step 3: Start the app
65
81
 
@@ -182,7 +182,7 @@ fragment RowRenderer on Page @inject(into: ["HygraphPage"]) {
182
182
  - Add a new file, /components/GraphCMS/Banner/index.tsx:
183
183
 
184
184
  ```tsx
185
- import { RichText } from '@graphcommerce/graphcms-ui'
185
+ import { RichText } from '@graphcommerce/hygraph-ui'
186
186
  import { BannerFragment } from './Banner.gql'
187
187
 
188
188
  export function Banner(props: BannerFragment) {
@@ -0,0 +1,355 @@
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.
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
+ ## Prevent components outside of the viewport from unnecessarily hydrating
111
+
112
+ To keep the Hydration phase as short as possible, it's a good idea to postpone
113
+ the hydration of (heavy) components that are outside of the viewport until they
114
+ are needed. By using `<LazyHydrate>`, the component can be hydrated either
115
+ manually, by setting the `hydrated` prop to true or automatically by the
116
+ IntersectionObserver inside `<LazyHydrate>` (as soon as the component comes into
117
+ view). It is recommended to set an estimated height value on the `height` prop.
118
+ This will reserver space for the component, when the page is loaded clientside.
119
+ This is to prevent multiple `LazyHydrate` components below each other from
120
+ intersecting all at once (as they do not have any HTML, and thus any height,
121
+ when loaded clientside). This height does not need to be exact.
122
+
123
+ The IntersectionObserver method of hydrating should never be used for components
124
+ that are inside the viewport on pageload, as it requires JS and is asynchronous.
125
+ When items are rendered in a loop and the first few items are inside the
126
+ viewport, these should be set to `hydrated={true}` based on the index of the
127
+ loop.
128
+
129
+ Example 1:
130
+
131
+ ```tsx
132
+ import { LazyHydrate } from '@graphcommerce/next-ui'
133
+
134
+ function MyLayout() {
135
+ return (
136
+ <MyComponentAboveTheFold>
137
+ Immediately hydrated
138
+ </MyComponentAboveTheFold>
139
+
140
+ <LazyHydrate hydrated={true}>
141
+ <MyComponentAboveTheFold>
142
+ Immediately hydrated
143
+ </MyComponentAboveTheFold>
144
+ </<LazyHydrate>
145
+
146
+ <LazyHydrate height={500}>
147
+ <MyExpensiveComponentBelowTheFold>
148
+ Only hydrated when scrolled into view
149
+ </MyExpensiveComponentBelowTheFold>
150
+ </LazyHydrate>
151
+ )
152
+ }
153
+ ```
154
+
155
+ Example 2:
156
+
157
+ ```tsx
158
+ export function MyItems(props) {
159
+ const { items, loadingEager = 2 } = props
160
+
161
+ return (
162
+ <>
163
+ {items.map((item, index) => (
164
+ // The first two items are hydrated immediately. Any items after that are hydrated on intersection
165
+ <LazyHydrate
166
+ key={item.id}
167
+ hydrated={index < loadingEager ? true : undefined}
168
+ height={500}
169
+ >
170
+ <MyComponent {...item} />
171
+ </LazyHydrate>
172
+ ))}
173
+ </>
174
+ )
175
+ }
176
+ ```
177
+
178
+ ## Conditionally render on mobile/desktop
179
+
180
+ TLDR: Use the `<MediaQuery>` component instead of useMediaQuery or CSS
181
+ breakpoints.
182
+
183
+ To render UI conditionally for various breakpoints it is practically always
184
+ faster to render them conditionally with CSS than it is to conditionallty render
185
+ components with JS. However rendering conditionally with CSS will cause
186
+ increased JS executing time, Total Blocking Time and possibly INP issues.
187
+
188
+ ### Conditionally render with JS: useMediaQuery
189
+
190
+ useMediaQuery: When you are now using useMediaQuery to conditionally render
191
+ content for mobile or desktop.
192
+
193
+ This means that hooks like useMediaQuery should almost never be used.
194
+ [See docs](https://mui.com/material-ui/react-use-media-query/#server-side-rendering)
195
+ and [examples](https://mui.com/system/display/#hiding-elements).
196
+
197
+ > Server-side rendering and client-side media queries are fundamentally at odds.
198
+ > Be aware of the tradeoff.
199
+
200
+ Also see
201
+ https://mui.com/material-ui/react-use-media-query/#server-side-rendering
202
+
203
+ 1. Is very slow as it has to wait for the JS to initialize on pageload.
204
+ 2. Can cause CLS problems if the useMediaQuery is used to render elements in
205
+ the viewport.
206
+ 3. Can cause LCP issues if useMediaQuery is used to render the LCP element.
207
+ 4. Causes TBT problems as a component always needs to be rerendered. (And bad
208
+ TBT can cause INP problems)
209
+ 5. HTML isn't present in the DOM, which can cause SEO issues.
210
+
211
+ ### Conditionally render with CSS: CSS Media query
212
+
213
+ When you are using CSS to show or hide content based on media queries. Causes
214
+ TBT problems as both code paths need to be rendered. Bad TBT can cause INP
215
+ problems.
216
+
217
+ ```tsx
218
+ function RenderConditionallyForCertainBreakpoints() {
219
+ return (
220
+ <>
221
+ <Box sx={{ display: { xs: 'block', md: 'none' } }}>
222
+ hide on screens wider than md
223
+ </Box>
224
+ <Box sx={{ display: { xs: 'none', md: 'block' } }}>
225
+ hide on screens smaller than md
226
+ </Box>
227
+ </>
228
+ )
229
+ }
230
+ ```
231
+
232
+ ### MediaQuery component
233
+
234
+ To solve both of the above problems we can use the `<MediaQuery>` component. It
235
+ will conditionally render/hydrate the component based on the media query and not
236
+ execute the JS if the media query does not match.
237
+
238
+ 1. On the server both code paths are rendered as normal, like you would with the
239
+ conditional render with CSS. On the first browser render (where JS is loaded)
240
+ it will conditionally show the component based on the CSS media query.
241
+ 2. During hydration the component will be hydrated only if the media query
242
+ matches. If the media query doesn't match it will not be hydrated (and thus
243
+ not execute
244
+ the JS).
245
+ 3. When the media query matches the component will rerender and show the
246
+ component.
247
+ 4. When components are created on the client, they are conditionally rendered.
248
+
249
+ Example:
250
+
251
+ ```tsx
252
+ import { MediaQuery } from '@graphcommerce/next-ui'
253
+
254
+ function MyLayout() {
255
+ return (
256
+ <MediaQuery query={(theme) => theme.breakpoints.up('md')}>
257
+ <MyExpensiveDesktopComponent>
258
+ Only visisble on desktop
259
+ </MyExpensiveDesktopComponent>
260
+ </MediaQuery>
261
+ )
262
+ }
263
+ ```
264
+
265
+ ## Use Context + useState cautiously
266
+
267
+ Contexts + useState should be used cautiously. When the state updates, it causes
268
+ ALL components within the context provider to rerender, not just the components
269
+ that use the context. When wrapping a lot of components inside a context, this
270
+ can become very expensive.
271
+
272
+ Solution for state that involves query data: Use Apollo Client cache, by using
273
+ `useQuery` for data that is already fetched on the server and cached by Apollo.
274
+
275
+ Solution for local state: use
276
+ [Apollo Reactive variables](https://www.apollographql.com/docs/react/local-state/reactive-variables)
277
+
278
+ ## Image resizing
279
+
280
+ When using the Image component, the sizes prop should be set correctly so no
281
+ unnecessarily large images are served. The prop accepts either a string (vw, px,
282
+ calc(), etc) or an object with breakpoints and a string value. See the
283
+ [prop type](https://github.com/graphcommerce-org/graphcommerce/blob/canary/packages/image/components/Image.tsx#L176-L185)
284
+ for all available options.
285
+
286
+ Example:
287
+
288
+ ```tsx
289
+ <Image
290
+ layout='fill'
291
+ alt={item.label}
292
+ src={item.url}
293
+ sizes={{
294
+ 0: '100vw',
295
+ [theme.breakpoints.values.md]: '50vw',
296
+ }}
297
+ />
298
+ ```
299
+
300
+ ## Preventing a query waterfall
301
+
302
+ When querying inside `getStaticProps`, only use `await` when the data is
303
+ required for something else inside `getStaticProps`. Otherwise it should be
304
+ awaited in the return statement. When a query is awaited immediately, it halts
305
+ the code at that point until that query completes. Meaning any queries called
306
+ after that will be called in serial. Awaiting all queries in the return
307
+ statement allows the queries to be called in parallel.
308
+
309
+ Example:
310
+
311
+ ```tsx
312
+ // Bad, because each query needs to wait until the previous one is completed entirely before being fired
313
+ export const getStaticProps: GetPageStaticProps = async (context) => {
314
+ // Query takes 100ms - code is halted until it finishes
315
+ const {data: queryOne} = await staticClient.query({ query: queryOneDocument })
316
+ // Query takes 100ms - code is halted until it finishes
317
+ const {data: queryTwo} = await staticClient.query({ query: queryTwoDocument })
318
+ // Query takes 100ms - code is halted until it finishes
319
+ const {data: queryThree} = await staticClient.query({ query: queryThreeDocument })
320
+
321
+
322
+ return {
323
+ props: {
324
+ ...queryOne
325
+ ...queryTwo
326
+ ...queryThree
327
+ // Total wait time 300ms
328
+ },
329
+ }
330
+ }
331
+ ```
332
+
333
+ ```tsx
334
+ // Good, because each query is fired in parallel and only awaited when each query is already running in the background
335
+ export const getStaticProps: GetPageStaticProps = async (context) => {
336
+ // Query takes 100ms - but it runs in the background, code continues execution
337
+ const queryOne = staticClient.query({ query: queryOneDocument })
338
+ // Query takes 100ms - but it runs in the background, code continues execution
339
+ const queryTwo = staticClient.query({ query: queryTwoDocument })
340
+ // Query takes 100ms - but it runs in the background, code continues execution
341
+ const queryThree = staticClient.query({ query: queryThreeDocument })
342
+
343
+ return {
344
+ props: {
345
+ // Awaits query (100ms)
346
+ ...(await queryOne).data
347
+ // Awaits query, already finished in the background (0ms)
348
+ ...(await queryTwo).data
349
+ // Awaits query, already finished in the background (0ms)
350
+ ...(await queryThree).data
351
+ // Total wait time 100ms
352
+ },
353
+ }
354
+ }
355
+ ```
@@ -152,7 +152,7 @@ import { PageOptions } from '@graphcommerce/framer-next-pages'
152
152
  import {
153
153
  hygraphPageContent,
154
154
  HygraphPagesQuery,
155
- } from '@graphcommerce/graphcms-ui'
155
+ } from '@graphcommerce/hygraph-ui'
156
156
  import { StoreConfigDocument } from '@graphcommerce/magento-store'
157
157
  import {
158
158
  GetStaticProps,
@@ -61,8 +61,8 @@ https://user-images.githubusercontent.com/1251986/227236765-503ccaac-6499-48df-b
61
61
  ## Page routing
62
62
 
63
63
  GraphCommerce uses Next.js file-based
64
- [page routing ↗](https://nextjs.org/docs/routing/introduction). The files inside
65
- the `📁 /pages` directory handle routing. Modify these files to meet your
64
+ [page routing ↗](https://nextjs.org/docs/routing/introduction). The files
65
+ inside the `📁 /pages` directory handle routing. Modify these files to meet your
66
66
  requirements or [build a custom page](./pages.md).
67
67
 
68
68
  - Product pages: `📄 /p/[...url].tsx`