shopify 3.93.1 → 3.93.2

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.
Files changed (39) hide show
  1. package/dist/assets/hydrogen/starter/CHANGELOG.md +64 -0
  2. package/dist/assets/hydrogen/starter/app/components/Aside.tsx +4 -2
  3. package/dist/assets/hydrogen/starter/app/components/CartMain.tsx +6 -3
  4. package/dist/assets/hydrogen/starter/app/components/CartSummary.tsx +117 -23
  5. package/dist/assets/hydrogen/starter/app/components/Header.tsx +3 -3
  6. package/dist/assets/hydrogen/starter/app/components/PaginatedResourceSection.tsx +24 -4
  7. package/dist/assets/hydrogen/starter/app/components/ProductPrice.tsx +1 -1
  8. package/dist/assets/hydrogen/starter/app/routes/[robots.txt].tsx +14 -53
  9. package/dist/assets/hydrogen/starter/app/routes/_index.tsx +11 -4
  10. package/dist/assets/hydrogen/starter/app/routes/account.$.tsx +1 -1
  11. package/dist/assets/hydrogen/starter/app/routes/account.addresses.tsx +2 -2
  12. package/dist/assets/hydrogen/starter/app/routes/account.profile.tsx +1 -1
  13. package/dist/assets/hydrogen/starter/package.json +5 -5
  14. package/dist/assets/hydrogen/starter/storefrontapi.generated.d.ts +0 -11
  15. package/dist/assets/hydrogen/starter/vite.config.ts +1 -1
  16. package/dist/assets/hydrogen/vite/vite.config.js +1 -1
  17. package/dist/{chunk-WOERFYNW.js → chunk-4QL77VYJ.js} +2 -2
  18. package/dist/{chunk-TCRHJ3ZH.js → chunk-4VZV4LQX.js} +2 -2
  19. package/dist/{chunk-SVYSLNQH.js → chunk-5FCKEHCK.js} +177 -181
  20. package/dist/{chunk-VLDSGLBP.js → chunk-DDTYWTF2.js} +1 -1
  21. package/dist/{chunk-XV44IQDO.js → chunk-FYQIRCLV.js} +1 -1
  22. package/dist/{chunk-T57REQVZ.js → chunk-IG47ZDRU.js} +1 -1
  23. package/dist/{chunk-PB3UDYWH.js → chunk-MX6WWR5F.js} +1 -1
  24. package/dist/{chunk-P3ASN7B5.js → chunk-XVFYDYZA.js} +1 -1
  25. package/dist/{error-handler-54XVSWV5.js → error-handler-GZ2I7BG5.js} +1 -1
  26. package/dist/hooks/postrun.js +1 -1
  27. package/dist/hooks/prerun.js +1 -1
  28. package/dist/{http-proxy-node16-KBILO6A6.js → http-proxy-node16-DSQMBVDI.js} +1 -1
  29. package/dist/index.js +753 -753
  30. package/dist/{lib-EN3PX6IK.js → lib-GGVLMXY5.js} +1 -1
  31. package/dist/{local-JCUIPKND.js → local-WHQ3ZS4K.js} +1 -1
  32. package/dist/{node-package-manager-P7JQBCHZ.js → node-package-manager-6XMPTNUI.js} +1 -1
  33. package/dist/tsconfig.tsbuildinfo +1 -1
  34. package/dist/{ui-XTVYPIVY.js → ui-GZ7DOSHP.js} +1 -1
  35. package/dist/{workerd-3GJRSBJN.js → workerd-LJU6AVMQ.js} +1 -1
  36. package/oclif.manifest.json +2 -3
  37. package/package.json +7 -7
  38. package/dist/assets/hydrogen/starter/app/routes/api.$version.[graphql.json].tsx +0 -14
  39. /package/dist/{morph-6DCXNO2H.js → morph-Q32V442A.js} +0 -0
@@ -1,5 +1,69 @@
1
1
  # skeleton
2
2
 
3
+ ## 2026.4.0
4
+
5
+ ### Major Changes
6
+
7
+ - Update Storefront API and Customer Account API from 2026-01 to 2026-04. ([#3651](https://github.com/Shopify/hydrogen/pull/3651)) by [@itsjustriley](https://github.com/itsjustriley)
8
+
9
+ ## Breaking changes
10
+
11
+ **JSON metafield values limited to 128KB**: When using API version 2026-04 or later, the Storefront API limits JSON type metafield writes to 128KB. This limit applies at the API level - Hydrogen passes through to the Storefront API without additional restriction. Apps that used JSON metafields before April 1, 2026 are grandfathered at the existing 2MB limit. Large metafield values continue to be readable by all API versions.
12
+
13
+ ## New features
14
+
15
+ **New `MERCHANDISE_LINE_TRANSFORMERS_RUN_ERROR` cart error code**: Cart operations (`cartCreate`, `cartLinesAdd`, etc.) now return a specific `MERCHANDISE_LINE_TRANSFORMERS_RUN_ERROR` error code when a Cart Transform Function fails at runtime, instead of the previous generic `INVALID` error code. If you handle cart errors in your storefront code, you may want to add handling for this new code.
16
+
17
+ ## Changelog links
18
+ - [Storefront API 2026-04 changelog](https://shopify.dev/changelog?filter=api&api_version=2026-04&api_type=storefront-graphql)
19
+ - [Customer Account API 2026-04 changelog](https://shopify.dev/changelog?filter=api&api_version=2026-04&api_type=customer-account-graphql)
20
+
21
+ ### Patch Changes
22
+
23
+ - Updated dependencies [[`e7215ed0d7a74cacfa8c935f414c1cf32fc2ccd0`](https://github.com/Shopify/hydrogen/commit/e7215ed0d7a74cacfa8c935f414c1cf32fc2ccd0), [`92ab8e8b59807bbdb5dbecaa18629292d5566135`](https://github.com/Shopify/hydrogen/commit/92ab8e8b59807bbdb5dbecaa18629292d5566135), [`b0caa5c013380c7837f049f48da089a1671e2c6d`](https://github.com/Shopify/hydrogen/commit/b0caa5c013380c7837f049f48da089a1671e2c6d)]:
24
+ - @shopify/hydrogen@2026.4.0
25
+
26
+ ## 2026.1.4
27
+
28
+ ### Patch Changes
29
+
30
+ - Remove redundant Storefront API proxy route from skeleton template. The server now automatically proxies requests to `/api/:version/graphql.json` via `createRequestHandler` with `proxyStandardRoutes: true` (enabled by default since December 2025). ([#3572](https://github.com/Shopify/hydrogen/pull/3572)) by [@itsjustriley](https://github.com/itsjustriley)
31
+
32
+ Developers no longer need a manual route file for the tokenless Storefront API. Existing apps with this route can safely delete it - the server-level proxy provides the same functionality with better cookie forwarding and analytics integration.
33
+
34
+ - Updated dependencies [[`b0a75c1d759706931876f056662de2497cb3e688`](https://github.com/Shopify/hydrogen/commit/b0a75c1d759706931876f056662de2497cb3e688), [`a44ee3566b9bb9a7f43f05dfaae6f1f2ab1d548f`](https://github.com/Shopify/hydrogen/commit/a44ee3566b9bb9a7f43f05dfaae6f1f2ab1d548f)]:
35
+ - @shopify/hydrogen@2026.1.4
36
+
37
+ ## 2026.1.3
38
+
39
+ ### Patch Changes
40
+
41
+ - Improve screen reader experience for paginated product grids by hiding decorative arrow characters from assistive technology. ([#3557](https://github.com/Shopify/hydrogen/pull/3557)) by [@itsjustriley](https://github.com/itsjustriley)
42
+
43
+ - Fix broken `aria-label` on territory code input in address form. The label was the raw developer string `"territoryCode"` instead of a human-readable `"Country code"`. ([#3607](https://github.com/Shopify/hydrogen/pull/3607)) by [@itsjustriley](https://github.com/itsjustriley)
44
+
45
+ - Add aria-label to ProductPrice for improved screen reader accessibility ([#3558](https://github.com/Shopify/hydrogen/pull/3558)) by [@itsjustriley](https://github.com/itsjustriley)
46
+
47
+ - Updated dependencies [[`108243003a7f36349a446478f4e8ab0cade3e13a`](https://github.com/Shopify/hydrogen/commit/108243003a7f36349a446478f4e8ab0cade3e13a)]:
48
+ - @shopify/hydrogen@2026.1.3
49
+
50
+ ## 2026.1.2
51
+
52
+ ### Patch Changes
53
+
54
+ - Improve gift card accessibility in Skeleton template ([#3518](https://github.com/Shopify/hydrogen/pull/3518)) by [@itsjustriley](https://github.com/itsjustriley)
55
+
56
+ - Updated shopify/cli dependencies for cli-hydrogen ([#3553](https://github.com/Shopify/hydrogen/pull/3553)) by [@andguy95](https://github.com/andguy95)
57
+
58
+ - Updated loaders that used `customerAccount.handleAuthStatus()` to now await it. ([#3523](https://github.com/Shopify/hydrogen/pull/3523)) by [@fredericoo](https://github.com/fredericoo)
59
+
60
+ ### Migration
61
+
62
+ If you call `handleAuthStatus()` in your own loaders, update those callsites to use `await`.
63
+
64
+ - Updated dependencies [[`16b0e7baca0dfd1fb330d12dac924c7593d169a8`](https://github.com/Shopify/hydrogen/commit/16b0e7baca0dfd1fb330d12dac924c7593d169a8), [`1c19b87782818dbdb4252754d2d44eb9a44fe50f`](https://github.com/Shopify/hydrogen/commit/1c19b87782818dbdb4252754d2d44eb9a44fe50f), [`029fa2d0e2297f67b37c650ba8e875ee5dee81b3`](https://github.com/Shopify/hydrogen/commit/029fa2d0e2297f67b37c650ba8e875ee5dee81b3)]:
65
+ - @shopify/hydrogen@2026.1.2
66
+
3
67
  ## 2026.1.1
4
68
 
5
69
  ### Patch Changes
@@ -5,6 +5,7 @@ import {
5
5
  useEffect,
6
6
  useState,
7
7
  } from 'react';
8
+ import {useId} from 'react';
8
9
 
9
10
  type AsideType = 'search' | 'cart' | 'mobile' | 'closed';
10
11
  type AsideContextValue = {
@@ -34,7 +35,7 @@ export function Aside({
34
35
  }) {
35
36
  const {type: activeType, close} = useAside();
36
37
  const expanded = type === activeType;
37
-
38
+ const id = useId();
38
39
  useEffect(() => {
39
40
  const abortController = new AbortController();
40
41
 
@@ -57,11 +58,12 @@ export function Aside({
57
58
  aria-modal
58
59
  className={`overlay ${expanded ? 'expanded' : ''}`}
59
60
  role="dialog"
61
+ aria-labelledby={id}
60
62
  >
61
63
  <button className="close-outside" onClick={close} />
62
64
  <aside>
63
65
  <header>
64
- <h3>{heading}</h3>
66
+ <h3 id={id}>{heading}</h3>
65
67
  <button className="close reset" onClick={close} aria-label="Close">
66
68
  &times;
67
69
  </button>
@@ -1,4 +1,4 @@
1
- import {useOptimisticCart, type OptimisticCartLine} from '@shopify/hydrogen';
1
+ import {useOptimisticCart} from '@shopify/hydrogen';
2
2
  import {Link} from 'react-router';
3
3
  import type {CartApiQueryFragment} from 'storefrontapi.generated';
4
4
  import {useAside} from '~/components/Aside';
@@ -50,7 +50,10 @@ export function CartMain({layout, cart: originalCart}: CartMainProps) {
50
50
  const childrenMap = getLineItemChildrenMap(cart?.lines?.nodes ?? []);
51
51
 
52
52
  return (
53
- <div className={className}>
53
+ <section
54
+ className={className}
55
+ aria-label={layout === 'page' ? 'Cart page' : 'Cart drawer'}
56
+ >
54
57
  <CartEmpty hidden={linesCount} layout={layout} />
55
58
  <div className="cart-details">
56
59
  <p id="cart-lines" className="sr-only">
@@ -79,7 +82,7 @@ export function CartMain({layout, cart: originalCart}: CartMainProps) {
79
82
  </div>
80
83
  {cartHasItems && <CartSummary cart={cart} layout={layout} />}
81
84
  </div>
82
- </div>
85
+ </section>
83
86
  );
84
87
  }
85
88
 
@@ -1,7 +1,7 @@
1
1
  import type {CartApiQueryFragment} from 'storefrontapi.generated';
2
2
  import type {CartLayout} from '~/components/CartMain';
3
3
  import {CartForm, Money, type OptimisticCart} from '@shopify/hydrogen';
4
- import {useEffect, useRef} from 'react';
4
+ import {useEffect, useId, useRef, useState} from 'react';
5
5
  import {useFetcher} from 'react-router';
6
6
 
7
7
  type CartSummaryProps = {
@@ -12,11 +12,16 @@ type CartSummaryProps = {
12
12
  export function CartSummary({cart, layout}: CartSummaryProps) {
13
13
  const className =
14
14
  layout === 'page' ? 'cart-summary-page' : 'cart-summary-aside';
15
+ const summaryId = useId();
16
+ const discountsHeadingId = useId();
17
+ const discountCodeInputId = useId();
18
+ const giftCardHeadingId = useId();
19
+ const giftCardInputId = useId();
15
20
 
16
21
  return (
17
- <div aria-labelledby="cart-summary" className={className}>
18
- <h4>Totals</h4>
19
- <dl className="cart-subtotal">
22
+ <div aria-labelledby={summaryId} className={className}>
23
+ <h4 id={summaryId}>Totals</h4>
24
+ <dl role="group" className="cart-subtotal">
20
25
  <dt>Subtotal</dt>
21
26
  <dd>
22
27
  {cart?.cost?.subtotalAmount?.amount ? (
@@ -26,8 +31,16 @@ export function CartSummary({cart, layout}: CartSummaryProps) {
26
31
  )}
27
32
  </dd>
28
33
  </dl>
29
- <CartDiscounts discountCodes={cart?.discountCodes} />
30
- <CartGiftCard giftCardCodes={cart?.appliedGiftCards} />
34
+ <CartDiscounts
35
+ discountCodes={cart?.discountCodes}
36
+ discountsHeadingId={discountsHeadingId}
37
+ discountCodeInputId={discountCodeInputId}
38
+ />
39
+ <CartGiftCard
40
+ giftCardCodes={cart?.appliedGiftCards}
41
+ giftCardHeadingId={giftCardHeadingId}
42
+ giftCardInputId={giftCardInputId}
43
+ />
31
44
  <CartCheckoutActions checkoutUrl={cart?.checkoutUrl} />
32
45
  </div>
33
46
  );
@@ -48,8 +61,12 @@ function CartCheckoutActions({checkoutUrl}: {checkoutUrl?: string}) {
48
61
 
49
62
  function CartDiscounts({
50
63
  discountCodes,
64
+ discountsHeadingId,
65
+ discountCodeInputId,
51
66
  }: {
52
67
  discountCodes?: CartApiQueryFragment['discountCodes'];
68
+ discountsHeadingId: string;
69
+ discountCodeInputId: string;
53
70
  }) {
54
71
  const codes: string[] =
55
72
  discountCodes
@@ -57,13 +74,17 @@ function CartDiscounts({
57
74
  ?.map(({code}) => code) || [];
58
75
 
59
76
  return (
60
- <div>
77
+ <section aria-label="Discounts">
61
78
  {/* Have existing discount, display it with a remove option */}
62
79
  <dl hidden={!codes.length}>
63
80
  <div>
64
- <dt>Discount(s)</dt>
81
+ <dt id={discountsHeadingId}>Discounts</dt>
65
82
  <UpdateDiscountForm>
66
- <div className="cart-discount">
83
+ <div
84
+ className="cart-discount"
85
+ role="group"
86
+ aria-labelledby={discountsHeadingId}
87
+ >
67
88
  <code>{codes?.join(', ')}</code>
68
89
  &nbsp;
69
90
  <button type="submit" aria-label="Remove discount">
@@ -77,11 +98,11 @@ function CartDiscounts({
77
98
  {/* Show an input to apply a discount */}
78
99
  <UpdateDiscountForm discountCodes={codes}>
79
100
  <div>
80
- <label htmlFor="discount-code-input" className="sr-only">
101
+ <label htmlFor={discountCodeInputId} className="sr-only">
81
102
  Discount code
82
103
  </label>
83
104
  <input
84
- id="discount-code-input"
105
+ id={discountCodeInputId}
85
106
  type="text"
86
107
  name="discountCode"
87
108
  placeholder="Discount code"
@@ -92,7 +113,7 @@ function CartDiscounts({
92
113
  </button>
93
114
  </div>
94
115
  </UpdateDiscountForm>
95
- </div>
116
+ </section>
96
117
  );
97
118
  }
98
119
 
@@ -118,52 +139,110 @@ function UpdateDiscountForm({
118
139
 
119
140
  function CartGiftCard({
120
141
  giftCardCodes,
142
+ giftCardHeadingId,
143
+ giftCardInputId,
121
144
  }: {
122
145
  giftCardCodes: CartApiQueryFragment['appliedGiftCards'] | undefined;
146
+ giftCardHeadingId: string;
147
+ giftCardInputId: string;
123
148
  }) {
124
149
  const giftCardCodeInput = useRef<HTMLInputElement>(null);
150
+ const removeButtonRefs = useRef<Map<string, HTMLButtonElement>>(new Map());
151
+ const previousCardIdsRef = useRef<string[]>([]);
125
152
  const giftCardAddFetcher = useFetcher({key: 'gift-card-add'});
153
+ const [removedCardIndex, setRemovedCardIndex] = useState<number | null>(null);
126
154
 
127
155
  useEffect(() => {
128
156
  if (giftCardAddFetcher.data) {
129
- giftCardCodeInput.current!.value = '';
157
+ if (giftCardCodeInput.current !== null) {
158
+ giftCardCodeInput.current.value = '';
159
+ }
130
160
  }
131
161
  }, [giftCardAddFetcher.data]);
132
162
 
163
+ useEffect(() => {
164
+ const currentCardIds = giftCardCodes?.map((card) => card.id) || [];
165
+
166
+ if (removedCardIndex !== null && giftCardCodes) {
167
+ const focusTargetIndex = Math.min(
168
+ removedCardIndex,
169
+ giftCardCodes.length - 1,
170
+ );
171
+ const focusTargetCard = giftCardCodes[focusTargetIndex];
172
+ const focusButton = focusTargetCard
173
+ ? removeButtonRefs.current.get(focusTargetCard.id)
174
+ : null;
175
+
176
+ if (focusButton) {
177
+ focusButton.focus();
178
+ } else if (giftCardCodeInput.current) {
179
+ giftCardCodeInput.current.focus();
180
+ }
181
+
182
+ setRemovedCardIndex(null);
183
+ }
184
+
185
+ previousCardIdsRef.current = currentCardIds;
186
+ }, [giftCardCodes, removedCardIndex]);
187
+
188
+ const handleRemoveClick = (cardId: string) => {
189
+ const index = previousCardIdsRef.current.indexOf(cardId);
190
+ if (index !== -1) {
191
+ setRemovedCardIndex(index);
192
+ }
193
+ };
194
+
133
195
  return (
134
- <div>
196
+ <section aria-label="Gift cards">
135
197
  {giftCardCodes && giftCardCodes.length > 0 && (
136
198
  <dl>
137
- <dt>Applied Gift Card(s)</dt>
199
+ <dt id={giftCardHeadingId}>Applied Gift Card(s)</dt>
138
200
  {giftCardCodes.map((giftCard) => (
139
- <RemoveGiftCardForm key={giftCard.id} giftCardId={giftCard.id}>
140
- <div className="cart-discount">
201
+ <dd key={giftCard.id} className="cart-discount">
202
+ <RemoveGiftCardForm
203
+ giftCardId={giftCard.id}
204
+ lastCharacters={giftCard.lastCharacters}
205
+ onRemoveClick={() => handleRemoveClick(giftCard.id)}
206
+ buttonRef={(el: HTMLButtonElement | null) => {
207
+ if (el) {
208
+ removeButtonRefs.current.set(giftCard.id, el);
209
+ } else {
210
+ removeButtonRefs.current.delete(giftCard.id);
211
+ }
212
+ }}
213
+ >
141
214
  <code>***{giftCard.lastCharacters}</code>
142
215
  &nbsp;
143
216
  <Money data={giftCard.amountUsed} />
144
- &nbsp;
145
- <button type="submit">Remove</button>
146
- </div>
147
- </RemoveGiftCardForm>
217
+ </RemoveGiftCardForm>
218
+ </dd>
148
219
  ))}
149
220
  </dl>
150
221
  )}
151
222
 
152
223
  <AddGiftCardForm fetcherKey="gift-card-add">
153
224
  <div>
225
+ <label htmlFor={giftCardInputId} className="sr-only">
226
+ Gift card code
227
+ </label>
154
228
  <input
229
+ id={giftCardInputId}
155
230
  type="text"
156
231
  name="giftCardCode"
157
232
  placeholder="Gift card code"
158
233
  ref={giftCardCodeInput}
159
234
  />
160
235
  &nbsp;
161
- <button type="submit" disabled={giftCardAddFetcher.state !== 'idle'}>
236
+ <button
237
+ type="submit"
238
+ disabled={giftCardAddFetcher.state !== 'idle'}
239
+ aria-label="Apply gift card code"
240
+ >
162
241
  Apply
163
242
  </button>
164
243
  </div>
165
244
  </AddGiftCardForm>
166
- </div>
245
+ </section>
167
246
  );
168
247
  }
169
248
 
@@ -187,10 +266,16 @@ function AddGiftCardForm({
187
266
 
188
267
  function RemoveGiftCardForm({
189
268
  giftCardId,
269
+ lastCharacters,
190
270
  children,
271
+ onRemoveClick,
272
+ buttonRef,
191
273
  }: {
192
274
  giftCardId: string;
275
+ lastCharacters: string;
193
276
  children: React.ReactNode;
277
+ onRemoveClick?: () => void;
278
+ buttonRef?: (el: HTMLButtonElement | null) => void;
194
279
  }) {
195
280
  return (
196
281
  <CartForm
@@ -201,6 +286,15 @@ function RemoveGiftCardForm({
201
286
  }}
202
287
  >
203
288
  {children}
289
+ &nbsp;
290
+ <button
291
+ type="submit"
292
+ aria-label={`Remove gift card ending in ${lastCharacters}`}
293
+ onClick={onRemoveClick}
294
+ ref={buttonRef}
295
+ >
296
+ Remove
297
+ </button>
204
298
  </CartForm>
205
299
  );
206
300
  }
@@ -136,7 +136,7 @@ function SearchToggle() {
136
136
  );
137
137
  }
138
138
 
139
- function CartBadge({count}: {count: number | null}) {
139
+ function CartBadge({count}: {count: number}) {
140
140
  const {open} = useAside();
141
141
  const {publish, shop, cart, prevCart} = useAnalytics();
142
142
 
@@ -154,14 +154,14 @@ function CartBadge({count}: {count: number | null}) {
154
154
  } as CartViewPayload);
155
155
  }}
156
156
  >
157
- Cart {count === null ? <span>&nbsp;</span> : count}
157
+ Cart <span aria-label={`(items: ${count})`}>{count}</span>
158
158
  </a>
159
159
  );
160
160
  }
161
161
 
162
162
  function CartToggle({cart}: Pick<HeaderProps, 'cart'>) {
163
163
  return (
164
- <Suspense fallback={<CartBadge count={null} />}>
164
+ <Suspense fallback={<CartBadge count={0} />}>
165
165
  <Await resolve={cart}>
166
166
  <CartBanner />
167
167
  </Await>
@@ -2,15 +2,17 @@ import * as React from 'react';
2
2
  import {Pagination} from '@shopify/hydrogen';
3
3
 
4
4
  /**
5
- * <PaginatedResourceSection > is a component that encapsulate how the previous and next behaviors throughout your application.
5
+ * <PaginatedResourceSection> encapsulates the previous and next pagination behaviors throughout your application.
6
6
  */
7
7
  export function PaginatedResourceSection<NodesType>({
8
8
  connection,
9
9
  children,
10
+ ariaLabel,
10
11
  resourcesClassName,
11
12
  }: {
12
13
  connection: React.ComponentProps<typeof Pagination<NodesType>>['connection'];
13
14
  children: React.FunctionComponent<{node: NodesType; index: number}>;
15
+ ariaLabel?: string;
14
16
  resourcesClassName?: string;
15
17
  }) {
16
18
  return (
@@ -23,15 +25,33 @@ export function PaginatedResourceSection<NodesType>({
23
25
  return (
24
26
  <div>
25
27
  <PreviousLink>
26
- {isLoading ? 'Loading...' : <span>↑ Load previous</span>}
28
+ {isLoading ? (
29
+ 'Loading...'
30
+ ) : (
31
+ <span>
32
+ <span aria-hidden="true">↑</span> Load previous
33
+ </span>
34
+ )}
27
35
  </PreviousLink>
28
36
  {resourcesClassName ? (
29
- <div className={resourcesClassName}>{resourcesMarkup}</div>
37
+ <div
38
+ aria-label={ariaLabel}
39
+ className={resourcesClassName}
40
+ role={ariaLabel ? 'region' : undefined}
41
+ >
42
+ {resourcesMarkup}
43
+ </div>
30
44
  ) : (
31
45
  resourcesMarkup
32
46
  )}
33
47
  <NextLink>
34
- {isLoading ? 'Loading...' : <span>Load more ↓</span>}
48
+ {isLoading ? (
49
+ 'Loading...'
50
+ ) : (
51
+ <span>
52
+ Load more <span aria-hidden="true">↓</span>
53
+ </span>
54
+ )}
35
55
  </NextLink>
36
56
  </div>
37
57
  );
@@ -9,7 +9,7 @@ export function ProductPrice({
9
9
  compareAtPrice?: MoneyV2 | null;
10
10
  }) {
11
11
  return (
12
- <div className="product-price">
12
+ <div aria-label="Price" className="product-price" role="group">
13
13
  {compareAtPrice ? (
14
14
  <div className="product-price-on-sale">
15
15
  {price ? <Money data={price} /> : null}
@@ -1,13 +1,8 @@
1
1
  import type {Route} from './+types/[robots.txt]';
2
- import {parseGid} from '@shopify/hydrogen';
3
2
 
4
- export async function loader({request, context}: Route.LoaderArgs) {
3
+ export function loader({request}: Route.LoaderArgs) {
5
4
  const url = new URL(request.url);
6
-
7
- const {shop} = await context.storefront.query(ROBOTS_QUERY);
8
-
9
- const shopId = parseGid(shop.id).id;
10
- const body = robotsTxtData({url: url.origin, shopId});
5
+ const body = robotsTxtData({url: url.origin});
11
6
 
12
7
  return new Response(body, {
13
8
  status: 200,
@@ -19,35 +14,31 @@ export async function loader({request, context}: Route.LoaderArgs) {
19
14
  });
20
15
  }
21
16
 
22
- function robotsTxtData({url, shopId}: {shopId?: string; url?: string}) {
17
+ function robotsTxtData({url}: {url?: string}) {
23
18
  const sitemapUrl = url ? `${url}/sitemap.xml` : undefined;
24
19
 
25
20
  return `
26
21
  User-agent: *
27
- ${generalDisallowRules({sitemapUrl, shopId})}
22
+ ${generalDisallowRules({sitemapUrl})}
28
23
 
29
24
  # Google adsbot ignores robots.txt unless specifically named!
30
25
  User-agent: adsbot-google
31
- Disallow: /checkouts/
32
- Disallow: /checkout
33
- Disallow: /carts
34
- Disallow: /orders
35
- ${shopId ? `Disallow: /${shopId}/checkouts` : ''}
36
- ${shopId ? `Disallow: /${shopId}/orders` : ''}
37
- Disallow: /*?*oseid=*
38
- Disallow: /*preview_theme_id*
39
- Disallow: /*preview_script_id*
26
+ Disallow: /cart
27
+ Disallow: /account
28
+ Disallow: /search
29
+ Allow: /search/
30
+ Disallow: /search/?*
40
31
 
41
32
  User-agent: Nutch
42
33
  Disallow: /
43
34
 
44
35
  User-agent: AhrefsBot
45
36
  Crawl-delay: 10
46
- ${generalDisallowRules({sitemapUrl, shopId})}
37
+ ${generalDisallowRules({sitemapUrl})}
47
38
 
48
39
  User-agent: AhrefsSiteAudit
49
40
  Crawl-delay: 10
50
- ${generalDisallowRules({sitemapUrl, shopId})}
41
+ ${generalDisallowRules({sitemapUrl})}
51
42
 
52
43
  User-agent: MJ12bot
53
44
  Crawl-Delay: 10
@@ -61,21 +52,8 @@ Crawl-delay: 1
61
52
  * This function generates disallow rules that generally follow what Shopify's
62
53
  * Online Store has as defaults for their robots.txt
63
54
  */
64
- function generalDisallowRules({
65
- shopId,
66
- sitemapUrl,
67
- }: {
68
- shopId?: string;
69
- sitemapUrl?: string;
70
- }) {
71
- return `Disallow: /admin
72
- Disallow: /cart
73
- Disallow: /orders
74
- Disallow: /checkouts/
75
- Disallow: /checkout
76
- ${shopId ? `Disallow: /${shopId}/checkouts` : ''}
77
- ${shopId ? `Disallow: /${shopId}/orders` : ''}
78
- Disallow: /carts
55
+ function generalDisallowRules({sitemapUrl}: {sitemapUrl?: string}) {
56
+ return `Disallow: /cart
79
57
  Disallow: /account
80
58
  Disallow: /collections/*sort_by*
81
59
  Disallow: /*/collections/*sort_by*
@@ -85,33 +63,16 @@ Disallow: /collections/*%2b*
85
63
  Disallow: /*/collections/*+*
86
64
  Disallow: /*/collections/*%2B*
87
65
  Disallow: /*/collections/*%2b*
88
- Disallow: */collections/*filter*&*filter*
66
+ Disallow: /*/collections/*filter*&*filter*
89
67
  Disallow: /blogs/*+*
90
68
  Disallow: /blogs/*%2B*
91
69
  Disallow: /blogs/*%2b*
92
70
  Disallow: /*/blogs/*+*
93
71
  Disallow: /*/blogs/*%2B*
94
72
  Disallow: /*/blogs/*%2b*
95
- Disallow: /*?*oseid=*
96
- Disallow: /*preview_theme_id*
97
- Disallow: /*preview_script_id*
98
73
  Disallow: /policies/
99
- Disallow: /*/*?*ls=*&ls=*
100
- Disallow: /*/*?*ls%3D*%3Fls%3D*
101
- Disallow: /*/*?*ls%3d*%3fls%3d*
102
74
  Disallow: /search
103
75
  Allow: /search/
104
76
  Disallow: /search/?*
105
- Disallow: /apple-app-site-association
106
- Disallow: /.well-known/shopify/monorail
107
77
  ${sitemapUrl ? `Sitemap: ${sitemapUrl}` : ''}`;
108
78
  }
109
-
110
- const ROBOTS_QUERY = `#graphql
111
- query StoreRobots($country: CountryCode, $language: LanguageCode)
112
- @inContext(country: $country, language: $language) {
113
- shop {
114
- id
115
- }
116
- }
117
- ` as const;
@@ -83,7 +83,11 @@ function FeaturedCollection({
83
83
  >
84
84
  {image && (
85
85
  <div className="featured-collection-image">
86
- <Image data={image} sizes="100vw" />
86
+ <Image
87
+ data={image}
88
+ sizes="100vw"
89
+ alt={image.altText || collection.title}
90
+ />
87
91
  </div>
88
92
  )}
89
93
  <h1>{collection.title}</h1>
@@ -97,8 +101,11 @@ function RecommendedProducts({
97
101
  products: Promise<RecommendedProductsQuery | null>;
98
102
  }) {
99
103
  return (
100
- <div className="recommended-products">
101
- <h2>Recommended Products</h2>
104
+ <section
105
+ className="recommended-products"
106
+ aria-labelledby="recommended-products"
107
+ >
108
+ <h2 id="recommended-products">Recommended Products</h2>
102
109
  <Suspense fallback={<div>Loading...</div>}>
103
110
  <Await resolve={products}>
104
111
  {(response) => (
@@ -113,7 +120,7 @@ function RecommendedProducts({
113
120
  </Await>
114
121
  </Suspense>
115
122
  <br />
116
- </div>
123
+ </section>
117
124
  );
118
125
  }
119
126
 
@@ -3,7 +3,7 @@ import type {Route} from './+types/account.$';
3
3
 
4
4
  // fallback wild card for all unauthenticated routes in account section
5
5
  export async function loader({context}: Route.LoaderArgs) {
6
- context.customerAccount.handleAuthStatus();
6
+ await context.customerAccount.handleAuthStatus();
7
7
 
8
8
  return redirect('/account');
9
9
  }
@@ -32,7 +32,7 @@ export const meta: Route.MetaFunction = () => {
32
32
  };
33
33
 
34
34
  export async function loader({context}: Route.LoaderArgs) {
35
- context.customerAccount.handleAuthStatus();
35
+ await context.customerAccount.handleAuthStatus();
36
36
 
37
37
  return {};
38
38
  }
@@ -468,7 +468,7 @@ export function AddressForm({
468
468
  />
469
469
  <label htmlFor="territoryCode">Country Code*</label>
470
470
  <input
471
- aria-label="territoryCode"
471
+ aria-label="Country code"
472
472
  autoComplete="country"
473
473
  defaultValue={address?.territoryCode ?? ''}
474
474
  id="territoryCode"
@@ -20,7 +20,7 @@ export const meta: Route.MetaFunction = () => {
20
20
  };
21
21
 
22
22
  export async function loader({context}: Route.LoaderArgs) {
23
- context.customerAccount.handleAuthStatus();
23
+ await context.customerAccount.handleAuthStatus();
24
24
 
25
25
  return {};
26
26
  }