@shopify/cli 3.61.0 → 3.61.1

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 (119) hide show
  1. package/dist/assets/hydrogen/starter/.eslintrc.cjs +1 -0
  2. package/dist/assets/hydrogen/starter/CHANGELOG.md +12 -74
  3. package/dist/assets/hydrogen/starter/app/components/Footer.tsx +3 -3
  4. package/dist/assets/hydrogen/starter/app/components/PageLayout.tsx +1 -1
  5. package/dist/assets/hydrogen/starter/app/root.tsx +77 -37
  6. package/dist/assets/hydrogen/starter/app/routes/_index.tsx +62 -25
  7. package/dist/assets/hydrogen/starter/app/routes/blogs.$blogHandle.$articleHandle.tsx +32 -6
  8. package/dist/assets/hydrogen/starter/app/routes/blogs.$blogHandle._index.tsx +36 -10
  9. package/dist/assets/hydrogen/starter/app/routes/blogs._index.tsx +35 -12
  10. package/dist/assets/hydrogen/starter/app/routes/collections.$handle.tsx +40 -7
  11. package/dist/assets/hydrogen/starter/app/routes/collections._index.tsx +32 -6
  12. package/dist/assets/hydrogen/starter/app/routes/collections.all.tsx +31 -6
  13. package/dist/assets/hydrogen/starter/app/routes/pages.$handle.tsx +36 -8
  14. package/dist/assets/hydrogen/starter/app/routes/products.$handle.tsx +50 -11
  15. package/dist/assets/hydrogen/starter/package.json +6 -7
  16. package/dist/assets/hydrogen/virtual-routes/components/RequestDetails.jsx +1 -2
  17. package/dist/assets/hydrogen/virtual-routes/components/RequestTable.jsx +1 -2
  18. package/dist/assets/hydrogen/virtual-routes/routes/index.jsx +1 -2
  19. package/dist/{chunk-LXXUWZRO.js → chunk-2OLSNRWX.js} +2 -2
  20. package/dist/{chunk-FFHJRSIP.js → chunk-4IJQUE7S.js} +1 -1
  21. package/dist/{chunk-2RKUO75O.js → chunk-4L5RRXWK.js} +129 -124
  22. package/dist/{chunk-75BJ33EP.js → chunk-5AOK2Y6Y.js} +2 -2
  23. package/dist/{chunk-4IXC46ZA.js → chunk-5CMD3IOB.js} +4 -4
  24. package/dist/{chunk-QX3YP6O5.js → chunk-5KRK4DLX.js} +2598 -795
  25. package/dist/{chunk-463OPQAS.js → chunk-72B3OWJW.js} +1 -1
  26. package/dist/{chunk-FWKRA64T.js → chunk-APKJSJ7J.js} +3 -3
  27. package/dist/{chunk-Q7PY7LVL.js → chunk-BV7GMWB6.js} +3 -3
  28. package/dist/{chunk-ABZKHSZ5.js → chunk-CJDUY6SJ.js} +1 -1
  29. package/dist/{chunk-7YJH7P7B.js → chunk-CWYZA7C7.js} +1 -1
  30. package/dist/{chunk-AL4KPZUN.js → chunk-CZ3HSQWY.js} +2 -2
  31. package/dist/{chunk-XRFOYWYI.js → chunk-D6WPLX6I.js} +2 -2
  32. package/dist/{chunk-EFL5CN6M.js → chunk-DDSNKVSE.js} +4 -4
  33. package/dist/{chunk-63QEL3SZ.js → chunk-EE47RXD7.js} +4 -4
  34. package/dist/{chunk-RW6YXHBL.js → chunk-FAJJWN6E.js} +12 -12
  35. package/dist/{chunk-OOAZSPYS.js → chunk-IH7T5JPW.js} +3 -3
  36. package/dist/{chunk-DWXXFI3U.js → chunk-IHBQYIUZ.js} +6 -6
  37. package/dist/{chunk-WNXHHHLZ.js → chunk-ITNVIYE3.js} +3 -3
  38. package/dist/{chunk-CMFKWJDJ.js → chunk-IWPRI3XU.js} +3 -3
  39. package/dist/{chunk-EDVQGTOL.js → chunk-K7AOF3FH.js} +4 -4
  40. package/dist/{chunk-2Q7SKRRM.js → chunk-K7RRB5XV.js} +162 -131
  41. package/dist/{chunk-3W2QB2OC.js → chunk-KKVOR377.js} +2 -2
  42. package/dist/{chunk-LY6UIYOG.js → chunk-KNQWUKO7.js} +2 -2
  43. package/dist/{chunk-ZOTASNQ2.js → chunk-MOWOUC6B.js} +1 -1
  44. package/dist/{chunk-ONJCU4C5.js → chunk-MUSUHPY2.js} +2 -2
  45. package/dist/{chunk-O53XNGJU.js → chunk-MVPEKELJ.js} +1 -1
  46. package/dist/{chunk-YZIPQNOG.js → chunk-MZ2TTN37.js} +2 -2
  47. package/dist/{chunk-2AH5EBXL.js → chunk-NNUPTNLQ.js} +4 -4
  48. package/dist/{chunk-UJQBYPRO.js → chunk-OC4AY5G7.js} +1 -1
  49. package/dist/{chunk-V67RXPI6.js → chunk-PMW2LD65.js} +3 -3
  50. package/dist/{chunk-5NBS5LAW.js → chunk-Q5FUGUDS.js} +1 -1
  51. package/dist/{chunk-TXDYKQZT.js → chunk-QMWGTSUE.js} +4 -4
  52. package/dist/{chunk-JZ5DN7RX.js → chunk-WLP3ZOM3.js} +2509 -728
  53. package/dist/{chunk-IIVL4DYB.js → chunk-WVPWWPI5.js} +2 -2
  54. package/dist/{chunk-XDCNK2SU.js → chunk-WZTRJKOI.js} +2 -2
  55. package/dist/{chunk-B4KDQK3X.js → chunk-XMZ6YUCX.js} +2 -2
  56. package/dist/{chunk-LUGC3D2G.js → chunk-YWPV7MV2.js} +6 -6
  57. package/dist/{chunk-HJ2JCW7E.js → chunk-Z5LDVTOK.js} +2 -2
  58. package/dist/cli/commands/auth/logout.js +18 -18
  59. package/dist/cli/commands/auth/logout.test.js +19 -19
  60. package/dist/cli/commands/debug/command-flags.js +17 -17
  61. package/dist/cli/commands/demo/catalog.js +18 -18
  62. package/dist/cli/commands/demo/generate-file.js +18 -18
  63. package/dist/cli/commands/demo/index.js +17 -17
  64. package/dist/cli/commands/demo/print-ai-prompt.js +18 -18
  65. package/dist/cli/commands/docs/generate.js +17 -17
  66. package/dist/cli/commands/docs/generate.test.js +17 -17
  67. package/dist/cli/commands/help.js +17 -17
  68. package/dist/cli/commands/kitchen-sink/async.js +18 -18
  69. package/dist/cli/commands/kitchen-sink/async.test.js +18 -18
  70. package/dist/cli/commands/kitchen-sink/index.js +20 -20
  71. package/dist/cli/commands/kitchen-sink/index.test.js +20 -20
  72. package/dist/cli/commands/kitchen-sink/prompts.js +18 -18
  73. package/dist/cli/commands/kitchen-sink/prompts.test.js +18 -18
  74. package/dist/cli/commands/kitchen-sink/static.js +18 -18
  75. package/dist/cli/commands/kitchen-sink/static.test.js +18 -18
  76. package/dist/cli/commands/search.js +18 -18
  77. package/dist/cli/commands/upgrade.js +17 -17
  78. package/dist/cli/commands/version.js +18 -18
  79. package/dist/cli/commands/version.test.js +18 -18
  80. package/dist/cli/services/commands/search.js +8 -8
  81. package/dist/cli/services/commands/search.test.js +8 -8
  82. package/dist/cli/services/commands/version.js +11 -11
  83. package/dist/cli/services/commands/version.test.js +12 -12
  84. package/dist/cli/services/demo.js +8 -8
  85. package/dist/cli/services/demo.test.js +8 -8
  86. package/dist/cli/services/kitchen-sink/async.js +8 -8
  87. package/dist/cli/services/kitchen-sink/prompts.js +8 -8
  88. package/dist/cli/services/kitchen-sink/static.js +8 -8
  89. package/dist/cli/services/upgrade.js +10 -10
  90. package/dist/cli/services/upgrade.test.js +11 -11
  91. package/dist/{chunk-5R5656YY.js → custom-oclif-loader-FPNRMQLJ.js} +18 -3
  92. package/dist/{custom-oclif-loader-KLTAWG4B-VOMMA3PO.js → custom-oclif-loader-RLNBRURI-YQ7IWI5Q.js} +4 -4
  93. package/dist/{error-handler-UJK7WNJE.js → error-handler-5ZOJ546L.js} +10 -10
  94. package/dist/{error-handler-ND7WBMC3-CRFJKAS4.js → error-handler-764JJPWK-SOMSJP6S.js} +6 -6
  95. package/dist/{error-handler-FUQWOW4J.js → error-handler-EOKEDDHC.js} +15 -15
  96. package/dist/hooks/postrun.js +13 -13
  97. package/dist/hooks/prerun.js +10 -10
  98. package/dist/index.js +309 -241
  99. package/dist/{lib-76RCE6WZ-AR3SQY6Y.js → lib-76RCE6WZ-5IYGTKKK.js} +2 -2
  100. package/dist/{lib-MWSNLG5P.js → lib-UYK3ZLZK.js} +3 -3
  101. package/dist/{local-3LWDOA7J-UFWE2V6L.js → local-GRWHBAAP-GLATK6HY.js} +4 -4
  102. package/dist/{local-5OND5PI5.js → local-S7EXCS5E.js} +7 -7
  103. package/dist/{morph-FQNWYET7.js → morph-XX23WEJZ.js} +25 -8
  104. package/dist/{node-WNJUHXTR.js → node-SSGKCHRY.js} +14 -14
  105. package/dist/{node-package-manager-ISP2P5R3.js → node-package-manager-V7QQWFES.js} +9 -9
  106. package/dist/{node-package-manager-TFY2ROCP-DDJAU44T.js → node-package-manager-VZCY35QS-AGCABGK7.js} +5 -5
  107. package/dist/{system-G445DF53.js → system-P6PSEW7S.js} +7 -7
  108. package/dist/tsconfig.tsbuildinfo +1 -1
  109. package/dist/{ui-ITQVVPJU.js → ui-JSBDAFMV.js} +7 -7
  110. package/dist/{ui-MUGCNPLG-UG4N3GMV.js → ui-P6NP425V-CKQLZD3E.js} +4 -4
  111. package/dist/{workerd-EZVOCUQW.js → workerd-3U2NNCIK.js} +13 -13
  112. package/oclif.manifest.json +1 -1
  113. package/package.json +4 -5
  114. package/dist/custom-oclif-loader-PJJ3H2WB.js +0 -28
  115. package/dist/hooks/init.d.ts +0 -12
  116. package/dist/hooks/init.js +0 -66
  117. package/dist/hooks/init.js.map +0 -1
  118. package/dist/{chunk-67MDUPX5.js → chunk-I434ZJQI.js} +7 -7
  119. package/dist/{chunk-74JT4RJX.js → chunk-VF4VWJWS.js} +3 -3
@@ -14,5 +14,6 @@ module.exports = {
14
14
  'no-useless-escape': 'off',
15
15
  '@typescript-eslint/no-non-null-asserted-optional-chain': 'off',
16
16
  'no-case-declarations': 'off',
17
+ 'no-console': ['warn', {allow: ['warn', 'error']}],
17
18
  },
18
19
  };
@@ -1,91 +1,29 @@
1
1
  # skeleton
2
2
 
3
- ## 0.0.0-next-ca7f288-20240530103543
3
+ ## 0.0.0-next-a8b9c6c-20240606114114
4
4
 
5
5
  ### Patch Changes
6
6
 
7
- - Update remix to v2.9.2 ([#2135](https://github.com/Shopify/hydrogen/pull/2135)) by [@michenly](https://github.com/michenly)
8
-
9
- - Refactor root to use [Remix's Layout Export pattern](https://remix.run/docs/en/main/file-conventions/root#layout-export). ([#2152](https://github.com/Shopify/hydrogen/pull/2152)) by [@michenly](https://github.com/michenly)
10
-
11
- This will also fix below error ahead of Single Fetch future flag.
12
-
13
- > You cannot `useLoaderData` in an errorElement
14
-
15
- The diff below showcase how you can make this refactor in your existing application.
16
-
17
- ```diff
18
- import {
19
- Outlet,
20
- - useLoaderData,
21
- + useRouteLoaderData,
22
- } from '@remix-run/react';
23
- -import {Layout} from '~/components/Layout';
24
- +import {PageLayout} from '~/components/PageLayout';
25
-
26
- -export default function App() {
27
- +export function Layout({children}: {children?: React.ReactNode}) {
28
- const nonce = useNonce();
29
- - const data = useLoaderData<typeof loader>();
30
- + const data = useRouteLoaderData<typeof loader>('root');
31
-
32
- return (
33
- <html>
34
- ...
35
- <body>
36
- - <Layout {...data}>
37
- - <Outlet />
38
- - </Layout>
39
- + {data? (
40
- + <PageLayout {...data}>
41
- + {children}
42
- + </PageLayout>
43
- + ) : (
44
- + children
45
- + )}
46
- </body>
47
- </html>
48
- );
49
- }
7
+ - Trigger changeset for all packages for next release
50
8
 
51
- +export default function App() {
52
- + return <Outlet />;
53
- +}
9
+ - Updated dependencies []:
10
+ - @shopify/hydrogen@0.0.0-next-a8b9c6c-20240606114114
11
+ - @shopify/cli-hydrogen@0.0.0-next-a8b9c6c-20240606114114
12
+ - @shopify/remix-oxygen@0.0.0-next-a8b9c6c-20240606114114
54
13
 
55
- export function ErrorBoundary() {
56
- const rootData = useLoaderData<typeof loader>();
14
+ ## 2024.4.5
57
15
 
58
- return (
59
- - <html>
60
- - ...
61
- - <body>
62
- - <Layout {...rootData}>
63
- - <div className="route-error">
64
- - <h1>Error</h1>
65
- - ...
66
- - </div>
67
- - </Layout>
68
- - </body>
69
- - </html>
70
- + <div className="route-error">
71
- + <h1>Error</h1>
72
- + ...
73
- + </div>
74
- );
75
- }
16
+ ### Patch Changes
76
17
 
77
- ```
18
+ - Update remix to v2.9.2 ([#2135](https://github.com/Shopify/hydrogen/pull/2135)) by [@michenly](https://github.com/michenly)
78
19
 
79
20
  - `<Analytics>` and `useAnalytics` are now stable. ([#2141](https://github.com/Shopify/hydrogen/pull/2141)) by [@wizardlyhel](https://github.com/wizardlyhel)
80
21
 
81
- - Trigger changeset for all packages for next release
82
-
83
22
  - Update the skeleton template to use React state in the aside dialogs ([#2088](https://github.com/Shopify/hydrogen/pull/2088)) by [@blittle](https://github.com/blittle)
84
23
 
85
- - Updated dependencies [[`fe82119f`](https://github.com/Shopify/hydrogen/commit/fe82119f5e75df5a0f727bab6a2186e679abc73d), [`32d4c33e`](https://github.com/Shopify/hydrogen/commit/32d4c33e4421a9c56f62a8c392f5417edddd0402), [`8eea75ec`](https://github.com/Shopify/hydrogen/commit/8eea75ec5ead4de98d7d1b2baedce8511029bcae), [`27e51abf`](https://github.com/Shopify/hydrogen/commit/27e51abfc1f5444afa952c503886bfa12fc55c7e), [`f29c9085`](https://github.com/Shopify/hydrogen/commit/f29c9085eb1adbde9e01226484eba8a85b5074ed), [`7b838beb`](https://github.com/Shopify/hydrogen/commit/7b838beb7c43380ffc9c32c2bb9f34291912fa42), [`ca4cf045`](https://github.com/Shopify/hydrogen/commit/ca4cf045f7fb72ad98b62af7bd172ff8cf553de2), [`5a554b2e`](https://github.com/Shopify/hydrogen/commit/5a554b2e9d91894c2db8032f0c29666dce1ea3f2), [`27e51abf`](https://github.com/Shopify/hydrogen/commit/27e51abfc1f5444afa952c503886bfa12fc55c7e), [`e29ecf03`](https://github.com/Shopify/hydrogen/commit/e29ecf03e158242d09045917dbef556bd456889a), [`5d6465b3`](https://github.com/Shopify/hydrogen/commit/5d6465b32d90052e5580fcb81d98427bcb8ad528), [`608389d6`](https://github.com/Shopify/hydrogen/commit/608389d6d69c6a9801935d528507c165d1dd4ccf), [`9dfd1cfe`](https://github.com/Shopify/hydrogen/commit/9dfd1cfeb3e96c6d3426427a4b5d97ef475dab6d), [`7def3e9f`](https://github.com/Shopify/hydrogen/commit/7def3e9fa6e28f4fde7af43e2f346aa32267c38e), [`65239c76`](https://github.com/Shopify/hydrogen/commit/65239c76ca1d0b294b59b1ad53624485859c4da5), [`ca7f2888`](https://github.com/Shopify/hydrogen/commit/ca7f28887367a4882e57a67c4e248b0f0bba1c9b)]:
86
- - @shopify/hydrogen@0.0.0-next-ca7f288-20240530103543
87
- - @shopify/cli-hydrogen@0.0.0-next-ca7f288-20240530103543
88
- - @shopify/remix-oxygen@0.0.0-next-ca7f288-20240530103543
24
+ - Updated dependencies [[`fe82119f`](https://github.com/Shopify/hydrogen/commit/fe82119f5e75df5a0f727bab6a2186e679abc73d), [`32d4c33e`](https://github.com/Shopify/hydrogen/commit/32d4c33e4421a9c56f62a8c392f5417edddd0402), [`8eea75ec`](https://github.com/Shopify/hydrogen/commit/8eea75ec5ead4de98d7d1b2baedce8511029bcae), [`27e51abf`](https://github.com/Shopify/hydrogen/commit/27e51abfc1f5444afa952c503886bfa12fc55c7e), [`f29c9085`](https://github.com/Shopify/hydrogen/commit/f29c9085eb1adbde9e01226484eba8a85b5074ed), [`7b838beb`](https://github.com/Shopify/hydrogen/commit/7b838beb7c43380ffc9c32c2bb9f34291912fa42), [`d702aec2`](https://github.com/Shopify/hydrogen/commit/d702aec2214646a78cdafc2c25d510489db73f6d), [`ca4cf045`](https://github.com/Shopify/hydrogen/commit/ca4cf045f7fb72ad98b62af7bd172ff8cf553de2), [`5a554b2e`](https://github.com/Shopify/hydrogen/commit/5a554b2e9d91894c2db8032f0c29666dce1ea3f2), [`27e51abf`](https://github.com/Shopify/hydrogen/commit/27e51abfc1f5444afa952c503886bfa12fc55c7e), [`5d6465b3`](https://github.com/Shopify/hydrogen/commit/5d6465b32d90052e5580fcb81d98427bcb8ad528), [`608389d6`](https://github.com/Shopify/hydrogen/commit/608389d6d69c6a9801935d528507c165d1dd4ccf), [`9dfd1cfe`](https://github.com/Shopify/hydrogen/commit/9dfd1cfeb3e96c6d3426427a4b5d97ef475dab6d), [`7def3e9f`](https://github.com/Shopify/hydrogen/commit/7def3e9fa6e28f4fde7af43e2f346aa32267c38e), [`65239c76`](https://github.com/Shopify/hydrogen/commit/65239c76ca1d0b294b59b1ad53624485859c4da5), [`ca7f2888`](https://github.com/Shopify/hydrogen/commit/ca7f28887367a4882e57a67c4e248b0f0bba1c9b)]:
25
+ - @shopify/hydrogen@2024.4.3
26
+ - @shopify/cli-hydrogen@8.1.0
89
27
 
90
28
  ## 2024.4.4
91
29
 
@@ -3,7 +3,7 @@ import {Await, NavLink} from '@remix-run/react';
3
3
  import type {FooterQuery, HeaderQuery} from 'storefrontapi.generated';
4
4
 
5
5
  interface FooterProps {
6
- footer: Promise<FooterQuery>;
6
+ footer: Promise<FooterQuery | null>;
7
7
  header: HeaderQuery;
8
8
  publicStoreDomain: string;
9
9
  }
@@ -18,7 +18,7 @@ export function Footer({
18
18
  <Await resolve={footerPromise}>
19
19
  {(footer) => (
20
20
  <footer className="footer">
21
- {footer.menu && header.shop.primaryDomain?.url && (
21
+ {footer?.menu && header.shop.primaryDomain?.url && (
22
22
  <FooterMenu
23
23
  menu={footer.menu}
24
24
  primaryDomainUrl={header.shop.primaryDomain.url}
@@ -37,7 +37,7 @@ function FooterMenu({
37
37
  primaryDomainUrl,
38
38
  publicStoreDomain,
39
39
  }: {
40
- menu: Awaited<FooterProps['footer']>['menu'];
40
+ menu: FooterQuery['menu'];
41
41
  primaryDomainUrl: FooterProps['header']['shop']['primaryDomain']['url'];
42
42
  publicStoreDomain: string;
43
43
  }) {
@@ -16,7 +16,7 @@ import {
16
16
 
17
17
  interface PageLayoutProps {
18
18
  cart: Promise<CartApiQueryFragment | null>;
19
- footer: Promise<FooterQuery>;
19
+ footer: Promise<FooterQuery | null>;
20
20
  header: HeaderQuery;
21
21
  isLoggedIn: Promise<boolean>;
22
22
  publicStoreDomain: string;
@@ -56,36 +56,20 @@ export function links() {
56
56
  ];
57
57
  }
58
58
 
59
- export async function loader({context}: LoaderFunctionArgs) {
60
- const {storefront, customerAccount, cart, env} = context;
61
- const publicStoreDomain = env.PUBLIC_STORE_DOMAIN;
59
+ export async function loader(args: LoaderFunctionArgs) {
60
+ // Start fetching non-critical data without blocking time to first byte
61
+ const deferredData = loadDeferredData(args);
62
62
 
63
- const isLoggedInPromise = customerAccount.isLoggedIn();
64
- const cartPromise = cart.get();
63
+ // Await the critical data required to render initial state of the page
64
+ const criticalData = await loadCriticalData(args);
65
65
 
66
- // defer the footer query (below the fold)
67
- const footerPromise = storefront.query(FOOTER_QUERY, {
68
- cache: storefront.CacheLong(),
69
- variables: {
70
- footerMenuHandle: 'footer', // Adjust to your footer menu handle
71
- },
72
- });
73
-
74
- // await the header query (above the fold)
75
- const headerPromise = storefront.query(HEADER_QUERY, {
76
- cache: storefront.CacheLong(),
77
- variables: {
78
- headerMenuHandle: 'main-menu', // Adjust to your header menu handle
79
- },
80
- });
66
+ const {storefront, env} = args.context;
81
67
 
82
68
  return defer(
83
69
  {
84
- cart: cartPromise,
85
- footer: footerPromise,
86
- header: await headerPromise,
87
- isLoggedIn: isLoggedInPromise,
88
- publicStoreDomain,
70
+ ...deferredData,
71
+ ...criticalData,
72
+ publicStoreDomain: env.PUBLIC_STORE_DOMAIN,
89
73
  shop: getShopAnalytics({
90
74
  storefront,
91
75
  publicStorefrontId: env.PUBLIC_STOREFRONT_ID,
@@ -97,13 +81,63 @@ export async function loader({context}: LoaderFunctionArgs) {
97
81
  },
98
82
  {
99
83
  headers: {
100
- 'Set-Cookie': await context.session.commit(),
84
+ 'Set-Cookie': await args.context.session.commit(),
101
85
  },
102
86
  },
103
87
  );
104
88
  }
105
89
 
106
- export function Layout({children}: {children?: React.ReactNode}) {
90
+ /**
91
+ * Load data necessary for rendering content above the fold. This is the critical data
92
+ * needed to render the page. If it's unavailable, the whole page should 400 or 500 error.
93
+ */
94
+ async function loadCriticalData({context}: LoaderFunctionArgs) {
95
+ const {storefront} = context;
96
+
97
+ const [header] = await Promise.all([
98
+ storefront.query(HEADER_QUERY, {
99
+ cache: storefront.CacheLong(),
100
+ variables: {
101
+ headerMenuHandle: 'main-menu', // Adjust to your header menu handle
102
+ },
103
+ }),
104
+ // Add other queries here, so that they are loaded in parallel
105
+ ]);
106
+
107
+ return {
108
+ header,
109
+ };
110
+ }
111
+
112
+ /**
113
+ * Load data for rendering content below the fold. This data is deferred and will be
114
+ * fetched after the initial page load. If it's unavailable, the page should still 200.
115
+ * Make sure to not throw any errors here, as it will cause the page to 500.
116
+ */
117
+ function loadDeferredData({context}: LoaderFunctionArgs) {
118
+ const {storefront, customerAccount, cart} = context;
119
+
120
+ // defer the footer query (below the fold)
121
+ const footer = storefront
122
+ .query(FOOTER_QUERY, {
123
+ cache: storefront.CacheLong(),
124
+ variables: {
125
+ footerMenuHandle: 'footer', // Adjust to your footer menu handle
126
+ },
127
+ })
128
+ .catch((error) => {
129
+ // Log query errors, but don't throw them so the page can still render
130
+ console.error(error);
131
+ return null;
132
+ });
133
+ return {
134
+ cart: cart.get(),
135
+ isLoggedIn: customerAccount.isLoggedIn(),
136
+ footer,
137
+ };
138
+ }
139
+
140
+ function Layout({children}: {children?: React.ReactNode}) {
107
141
  const nonce = useNonce();
108
142
  const data = useRouteLoaderData<RootLoader>('root');
109
143
 
@@ -135,7 +169,11 @@ export function Layout({children}: {children?: React.ReactNode}) {
135
169
  }
136
170
 
137
171
  export default function App() {
138
- return <Outlet />;
172
+ return (
173
+ <Layout>
174
+ <Outlet />
175
+ </Layout>
176
+ );
139
177
  }
140
178
 
141
179
  export function ErrorBoundary() {
@@ -151,14 +189,16 @@ export function ErrorBoundary() {
151
189
  }
152
190
 
153
191
  return (
154
- <div className="route-error">
155
- <h1>Oops</h1>
156
- <h2>{errorStatus}</h2>
157
- {errorMessage && (
158
- <fieldset>
159
- <pre>{errorMessage}</pre>
160
- </fieldset>
161
- )}
162
- </div>
192
+ <Layout>
193
+ <div className="route-error">
194
+ <h1>Oops</h1>
195
+ <h2>{errorStatus}</h2>
196
+ {errorMessage && (
197
+ <fieldset>
198
+ <pre>{errorMessage}</pre>
199
+ </fieldset>
200
+ )}
201
+ </div>
202
+ </Layout>
163
203
  );
164
204
  }
@@ -11,13 +11,48 @@ export const meta: MetaFunction = () => {
11
11
  return [{title: 'Hydrogen | Home'}];
12
12
  };
13
13
 
14
- export async function loader({context}: LoaderFunctionArgs) {
15
- const {storefront} = context;
16
- const {collections} = await storefront.query(FEATURED_COLLECTION_QUERY);
17
- const featuredCollection = collections.nodes[0];
18
- const recommendedProducts = storefront.query(RECOMMENDED_PRODUCTS_QUERY);
14
+ export async function loader(args: LoaderFunctionArgs) {
15
+ // Start fetching non-critical data without blocking time to first byte
16
+ const deferredData = loadDeferredData(args);
19
17
 
20
- return defer({featuredCollection, recommendedProducts});
18
+ // Await the critical data required to render initial state of the page
19
+ const criticalData = await loadCriticalData(args);
20
+
21
+ return defer({...deferredData, ...criticalData});
22
+ }
23
+
24
+ /**
25
+ * Load data necessary for rendering content above the fold. This is the critical data
26
+ * needed to render the page. If it's unavailable, the whole page should 400 or 500 error.
27
+ */
28
+ async function loadCriticalData({context}: LoaderFunctionArgs) {
29
+ const [{collections}] = await Promise.all([
30
+ context.storefront.query(FEATURED_COLLECTION_QUERY),
31
+ // Add other queries here, so that they are loaded in parallel
32
+ ]);
33
+
34
+ return {
35
+ featuredCollection: collections.nodes[0],
36
+ };
37
+ }
38
+
39
+ /**
40
+ * Load data for rendering content below the fold. This data is deferred and will be
41
+ * fetched after the initial page load. If it's unavailable, the page should still 200.
42
+ * Make sure to not throw any errors here, as it will cause the page to 500.
43
+ */
44
+ function loadDeferredData({context}: LoaderFunctionArgs) {
45
+ const recommendedProducts = context.storefront
46
+ .query(RECOMMENDED_PRODUCTS_QUERY)
47
+ .catch((error) => {
48
+ // Log query errors, but don't throw them so the page can still render
49
+ console.error(error);
50
+ return null;
51
+ });
52
+
53
+ return {
54
+ recommendedProducts,
55
+ };
21
56
  }
22
57
 
23
58
  export default function Homepage() {
@@ -55,32 +90,34 @@ function FeaturedCollection({
55
90
  function RecommendedProducts({
56
91
  products,
57
92
  }: {
58
- products: Promise<RecommendedProductsQuery>;
93
+ products: Promise<RecommendedProductsQuery | null>;
59
94
  }) {
60
95
  return (
61
96
  <div className="recommended-products">
62
97
  <h2>Recommended Products</h2>
63
98
  <Suspense fallback={<div>Loading...</div>}>
64
99
  <Await resolve={products}>
65
- {({products}) => (
100
+ {(response) => (
66
101
  <div className="recommended-products-grid">
67
- {products.nodes.map((product) => (
68
- <Link
69
- key={product.id}
70
- className="recommended-product"
71
- to={`/products/${product.handle}`}
72
- >
73
- <Image
74
- data={product.images.nodes[0]}
75
- aspectRatio="1/1"
76
- sizes="(min-width: 45em) 20vw, 50vw"
77
- />
78
- <h4>{product.title}</h4>
79
- <small>
80
- <Money data={product.priceRange.minVariantPrice} />
81
- </small>
82
- </Link>
83
- ))}
102
+ {response
103
+ ? response.products.nodes.map((product) => (
104
+ <Link
105
+ key={product.id}
106
+ className="recommended-product"
107
+ to={`/products/${product.handle}`}
108
+ >
109
+ <Image
110
+ data={product.images.nodes[0]}
111
+ aspectRatio="1/1"
112
+ sizes="(min-width: 45em) 20vw, 50vw"
113
+ />
114
+ <h4>{product.title}</h4>
115
+ <small>
116
+ <Money data={product.priceRange.minVariantPrice} />
117
+ </small>
118
+ </Link>
119
+ ))
120
+ : null}
84
121
  </div>
85
122
  )}
86
123
  </Await>
@@ -1,4 +1,4 @@
1
- import {json, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
1
+ import {defer, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
2
2
  import {useLoaderData, type MetaFunction} from '@remix-run/react';
3
3
  import {Image} from '@shopify/hydrogen';
4
4
 
@@ -6,16 +6,33 @@ export const meta: MetaFunction<typeof loader> = ({data}) => {
6
6
  return [{title: `Hydrogen | ${data?.article.title ?? ''} article`}];
7
7
  };
8
8
 
9
- export async function loader({params, context}: LoaderFunctionArgs) {
9
+ export async function loader(args: LoaderFunctionArgs) {
10
+ // Start fetching non-critical data without blocking time to first byte
11
+ const deferredData = loadDeferredData(args);
12
+
13
+ // Await the critical data required to render initial state of the page
14
+ const criticalData = await loadCriticalData(args);
15
+
16
+ return defer({...deferredData, ...criticalData});
17
+ }
18
+
19
+ /**
20
+ * Load data necessary for rendering content above the fold. This is the critical data
21
+ * needed to render the page. If it's unavailable, the whole page should 400 or 500 error.
22
+ */
23
+ async function loadCriticalData({context, params}: LoaderFunctionArgs) {
10
24
  const {blogHandle, articleHandle} = params;
11
25
 
12
26
  if (!articleHandle || !blogHandle) {
13
27
  throw new Response('Not found', {status: 404});
14
28
  }
15
29
 
16
- const {blog} = await context.storefront.query(ARTICLE_QUERY, {
17
- variables: {blogHandle, articleHandle},
18
- });
30
+ const [{blog}] = await Promise.all([
31
+ context.storefront.query(ARTICLE_QUERY, {
32
+ variables: {blogHandle, articleHandle},
33
+ }),
34
+ // Add other queries here, so that they are loaded in parallel
35
+ ]);
19
36
 
20
37
  if (!blog?.articleByHandle) {
21
38
  throw new Response(null, {status: 404});
@@ -23,7 +40,16 @@ export async function loader({params, context}: LoaderFunctionArgs) {
23
40
 
24
41
  const article = blog.articleByHandle;
25
42
 
26
- return json({article});
43
+ return {article};
44
+ }
45
+
46
+ /**
47
+ * Load data for rendering content below the fold. This data is deferred and will be
48
+ * fetched after the initial page load. If it's unavailable, the page should still 200.
49
+ * Make sure to not throw any errors here, as it will cause the page to 500.
50
+ */
51
+ function loadDeferredData({context}: LoaderFunctionArgs) {
52
+ return {};
27
53
  }
28
54
 
29
55
  export default function Article() {
@@ -1,4 +1,4 @@
1
- import {json, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
1
+ import {defer, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
2
2
  import {Link, useLoaderData, type MetaFunction} from '@remix-run/react';
3
3
  import {Image, Pagination, getPaginationVariables} from '@shopify/hydrogen';
4
4
  import type {ArticleItemFragment} from 'storefrontapi.generated';
@@ -7,10 +7,24 @@ export const meta: MetaFunction<typeof loader> = ({data}) => {
7
7
  return [{title: `Hydrogen | ${data?.blog.title ?? ''} blog`}];
8
8
  };
9
9
 
10
- export async function loader({
10
+ export async function loader(args: LoaderFunctionArgs) {
11
+ // Start fetching non-critical data without blocking time to first byte
12
+ const deferredData = loadDeferredData(args);
13
+
14
+ // Await the critical data required to render initial state of the page
15
+ const criticalData = await loadCriticalData(args);
16
+
17
+ return defer({...deferredData, ...criticalData});
18
+ }
19
+
20
+ /**
21
+ * Load data necessary for rendering content above the fold. This is the critical data
22
+ * needed to render the page. If it's unavailable, the whole page should 400 or 500 error.
23
+ */
24
+ async function loadCriticalData({
25
+ context,
11
26
  request,
12
27
  params,
13
- context: {storefront},
14
28
  }: LoaderFunctionArgs) {
15
29
  const paginationVariables = getPaginationVariables(request, {
16
30
  pageBy: 4,
@@ -20,18 +34,30 @@ export async function loader({
20
34
  throw new Response(`blog not found`, {status: 404});
21
35
  }
22
36
 
23
- const {blog} = await storefront.query(BLOGS_QUERY, {
24
- variables: {
25
- blogHandle: params.blogHandle,
26
- ...paginationVariables,
27
- },
28
- });
37
+ const [{blog}] = await Promise.all([
38
+ context.storefront.query(BLOGS_QUERY, {
39
+ variables: {
40
+ blogHandle: params.blogHandle,
41
+ ...paginationVariables,
42
+ },
43
+ }),
44
+ // Add other queries here, so that they are loaded in parallel
45
+ ]);
29
46
 
30
47
  if (!blog?.articles) {
31
48
  throw new Response('Not found', {status: 404});
32
49
  }
33
50
 
34
- return json({blog});
51
+ return {blog};
52
+ }
53
+
54
+ /**
55
+ * Load data for rendering content below the fold. This data is deferred and will be
56
+ * fetched after the initial page load. If it's unavailable, the page should still 200.
57
+ * Make sure to not throw any errors here, as it will cause the page to 500.
58
+ */
59
+ function loadDeferredData({context}: LoaderFunctionArgs) {
60
+ return {};
35
61
  }
36
62
 
37
63
  export default function Blog() {
@@ -1,4 +1,4 @@
1
- import {json, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
1
+ import {defer, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
2
2
  import {Link, useLoaderData, type MetaFunction} from '@remix-run/react';
3
3
  import {Pagination, getPaginationVariables} from '@shopify/hydrogen';
4
4
 
@@ -6,22 +6,45 @@ export const meta: MetaFunction = () => {
6
6
  return [{title: `Hydrogen | Blogs`}];
7
7
  };
8
8
 
9
- export const loader = async ({
10
- request,
11
- context: {storefront},
12
- }: LoaderFunctionArgs) => {
9
+ export async function loader(args: LoaderFunctionArgs) {
10
+ // Start fetching non-critical data without blocking time to first byte
11
+ const deferredData = loadDeferredData(args);
12
+
13
+ // Await the critical data required to render initial state of the page
14
+ const criticalData = await loadCriticalData(args);
15
+
16
+ return defer({...deferredData, ...criticalData});
17
+ }
18
+
19
+ /**
20
+ * Load data necessary for rendering content above the fold. This is the critical data
21
+ * needed to render the page. If it's unavailable, the whole page should 400 or 500 error.
22
+ */
23
+ async function loadCriticalData({context, request}: LoaderFunctionArgs) {
13
24
  const paginationVariables = getPaginationVariables(request, {
14
25
  pageBy: 10,
15
26
  });
16
27
 
17
- const {blogs} = await storefront.query(BLOGS_QUERY, {
18
- variables: {
19
- ...paginationVariables,
20
- },
21
- });
28
+ const [{blogs}] = await Promise.all([
29
+ context.storefront.query(BLOGS_QUERY, {
30
+ variables: {
31
+ ...paginationVariables,
32
+ },
33
+ }),
34
+ // Add other queries here, so that they are loaded in parallel
35
+ ]);
22
36
 
23
- return json({blogs});
24
- };
37
+ return {blogs};
38
+ }
39
+
40
+ /**
41
+ * Load data for rendering content below the fold. This data is deferred and will be
42
+ * fetched after the initial page load. If it's unavailable, the page should still 200.
43
+ * Make sure to not throw any errors here, as it will cause the page to 500.
44
+ */
45
+ function loadDeferredData({context}: LoaderFunctionArgs) {
46
+ return {};
47
+ }
25
48
 
26
49
  export default function Blogs() {
27
50
  const {blogs} = useLoaderData<typeof loader>();