@graphcommerce/docs 9.0.0-canary.98 → 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.
- package/CHANGELOG.md +88 -1102
- package/framework/caching.md +20 -9
- package/framework/config.md +32 -0
- package/framework/seo.md +68 -5
- package/getting-started/create.md +27 -11
- package/getting-started/graphcms-component.md +1 -1
- package/getting-started/improving-core-web-vitals.md +355 -0
- package/getting-started/pages.md +1 -1
- package/getting-started/readme.md +2 -2
- package/package.json +2 -2
- package/roadmap.md +61 -83
- package/upgrading/graphcommerce-8-to-9.md +18 -0
package/framework/caching.md
CHANGED
|
@@ -152,22 +152,33 @@ cache.
|
|
|
152
152
|
|
|
153
153
|
The service worker caches:
|
|
154
154
|
|
|
155
|
-
- [static fonts](https://github.com/
|
|
156
|
-
|
|
157
|
-
- [
|
|
158
|
-
|
|
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/
|
|
166
|
-
|
|
167
|
-
|
|
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
|
|
package/framework/config.md
CHANGED
|
@@ -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
|
|
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)
|
|
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
|
|
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
|
-
###
|
|
11
|
+
### Preparations
|
|
12
12
|
|
|
13
|
-
-
|
|
14
|
-
- Install and use node
|
|
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-
|
|
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`
|
|
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.
|
|
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
|
|
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/
|
|
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
|
+
```
|
package/getting-started/pages.md
CHANGED
|
@@ -152,7 +152,7 @@ import { PageOptions } from '@graphcommerce/framer-next-pages'
|
|
|
152
152
|
import {
|
|
153
153
|
hygraphPageContent,
|
|
154
154
|
HygraphPagesQuery,
|
|
155
|
-
} from '@graphcommerce/
|
|
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
|
|
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`
|