@shopify/create-hydrogen 4.3.13 → 5.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/dist/assets/hydrogen/bundle/analyzer.html +2045 -0
- package/dist/assets/hydrogen/i18n/domains.ts +28 -0
- package/dist/assets/hydrogen/i18n/mock-i18n-types.ts +3 -0
- package/dist/assets/hydrogen/i18n/subdomains.ts +27 -0
- package/dist/assets/hydrogen/i18n/subfolders.ts +29 -0
- package/dist/assets/hydrogen/routes/locale-check.ts +16 -0
- package/dist/assets/hydrogen/starter/.eslintignore +5 -0
- package/dist/assets/hydrogen/starter/.eslintrc.cjs +19 -0
- package/dist/assets/hydrogen/starter/.graphqlrc.yml +12 -0
- package/dist/assets/hydrogen/starter/CHANGELOG.md +709 -0
- package/dist/assets/hydrogen/starter/README.md +45 -0
- package/dist/assets/hydrogen/starter/app/assets/favicon.svg +28 -0
- package/dist/assets/hydrogen/starter/app/components/AddToCartButton.tsx +37 -0
- package/dist/assets/hydrogen/starter/app/components/Aside.tsx +76 -0
- package/dist/assets/hydrogen/starter/app/components/CartLineItem.tsx +150 -0
- package/dist/assets/hydrogen/starter/app/components/CartMain.tsx +68 -0
- package/dist/assets/hydrogen/starter/app/components/CartSummary.tsx +101 -0
- package/dist/assets/hydrogen/starter/app/components/Footer.tsx +129 -0
- package/dist/assets/hydrogen/starter/app/components/Header.tsx +230 -0
- package/dist/assets/hydrogen/starter/app/components/PageLayout.tsx +126 -0
- package/dist/assets/hydrogen/starter/app/components/ProductForm.tsx +80 -0
- package/dist/assets/hydrogen/starter/app/components/ProductImage.tsx +23 -0
- package/dist/assets/hydrogen/starter/app/components/ProductPrice.tsx +27 -0
- package/dist/assets/hydrogen/starter/app/components/Search.tsx +514 -0
- package/dist/assets/hydrogen/starter/app/entry.client.tsx +12 -0
- package/dist/assets/hydrogen/starter/app/entry.server.tsx +47 -0
- package/dist/assets/hydrogen/starter/app/graphql/customer-account/CustomerAddressMutations.ts +61 -0
- package/dist/assets/hydrogen/starter/app/graphql/customer-account/CustomerDetailsQuery.ts +40 -0
- package/dist/assets/hydrogen/starter/app/graphql/customer-account/CustomerOrderQuery.ts +87 -0
- package/dist/assets/hydrogen/starter/app/graphql/customer-account/CustomerOrdersQuery.ts +58 -0
- package/dist/assets/hydrogen/starter/app/graphql/customer-account/CustomerUpdateMutation.ts +24 -0
- package/dist/assets/hydrogen/starter/app/lib/fragments.ts +174 -0
- package/dist/assets/hydrogen/starter/app/lib/search.ts +29 -0
- package/dist/assets/hydrogen/starter/app/lib/session.ts +72 -0
- package/dist/assets/hydrogen/starter/app/lib/variants.ts +46 -0
- package/dist/assets/hydrogen/starter/app/root.tsx +191 -0
- package/dist/assets/hydrogen/starter/app/routes/$.tsx +11 -0
- package/dist/assets/hydrogen/starter/app/routes/[robots.txt].tsx +118 -0
- package/dist/assets/hydrogen/starter/app/routes/[sitemap.xml].tsx +177 -0
- package/dist/assets/hydrogen/starter/app/routes/_index.tsx +182 -0
- package/dist/assets/hydrogen/starter/app/routes/account.$.tsx +8 -0
- package/dist/assets/hydrogen/starter/app/routes/account._index.tsx +5 -0
- package/dist/assets/hydrogen/starter/app/routes/account.addresses.tsx +513 -0
- package/dist/assets/hydrogen/starter/app/routes/account.orders.$id.tsx +195 -0
- package/dist/assets/hydrogen/starter/app/routes/account.orders._index.tsx +107 -0
- package/dist/assets/hydrogen/starter/app/routes/account.profile.tsx +136 -0
- package/dist/assets/hydrogen/starter/app/routes/account.tsx +88 -0
- package/dist/assets/hydrogen/starter/app/routes/account_.authorize.tsx +5 -0
- package/dist/assets/hydrogen/starter/app/routes/account_.login.tsx +5 -0
- package/dist/assets/hydrogen/starter/app/routes/account_.logout.tsx +10 -0
- package/dist/assets/hydrogen/starter/app/routes/api.predictive-search.tsx +318 -0
- package/dist/assets/hydrogen/starter/app/routes/blogs.$blogHandle.$articleHandle.tsx +113 -0
- package/dist/assets/hydrogen/starter/app/routes/blogs.$blogHandle._index.tsx +188 -0
- package/dist/assets/hydrogen/starter/app/routes/blogs._index.tsx +119 -0
- package/dist/assets/hydrogen/starter/app/routes/cart.$lines.tsx +69 -0
- package/dist/assets/hydrogen/starter/app/routes/cart.tsx +102 -0
- package/dist/assets/hydrogen/starter/app/routes/collections.$handle.tsx +225 -0
- package/dist/assets/hydrogen/starter/app/routes/collections._index.tsx +146 -0
- package/dist/assets/hydrogen/starter/app/routes/collections.all.tsx +185 -0
- package/dist/assets/hydrogen/starter/app/routes/discount.$code.tsx +47 -0
- package/dist/assets/hydrogen/starter/app/routes/pages.$handle.tsx +84 -0
- package/dist/assets/hydrogen/starter/app/routes/policies.$handle.tsx +93 -0
- package/dist/assets/hydrogen/starter/app/routes/policies._index.tsx +63 -0
- package/dist/assets/hydrogen/starter/app/routes/products.$handle.tsx +299 -0
- package/dist/assets/hydrogen/starter/app/routes/search.tsx +177 -0
- package/dist/assets/hydrogen/starter/app/styles/app.css +486 -0
- package/dist/assets/hydrogen/starter/app/styles/reset.css +129 -0
- package/dist/assets/hydrogen/starter/customer-accountapi.generated.d.ts +509 -0
- package/dist/assets/hydrogen/starter/env.d.ts +54 -0
- package/dist/assets/hydrogen/starter/package.json +50 -0
- package/dist/assets/hydrogen/starter/public/.gitkeep +0 -0
- package/dist/assets/hydrogen/starter/server.ts +119 -0
- package/dist/assets/hydrogen/starter/storefrontapi.generated.d.ts +1211 -0
- package/dist/assets/hydrogen/starter/tsconfig.json +23 -0
- package/dist/assets/hydrogen/starter/vite.config.ts +41 -0
- package/dist/assets/hydrogen/tailwind/package.json +8 -0
- package/dist/assets/hydrogen/tailwind/tailwind.css +6 -0
- package/dist/assets/hydrogen/vanilla-extract/package.json +8 -0
- package/dist/assets/hydrogen/virtual-routes/assets/debug-network.css +592 -0
- package/dist/assets/hydrogen/virtual-routes/assets/favicon-dark.svg +20 -0
- package/dist/assets/hydrogen/virtual-routes/assets/favicon.svg +28 -0
- package/dist/assets/hydrogen/virtual-routes/assets/inter-variable-font.woff2 +0 -0
- package/dist/assets/hydrogen/virtual-routes/assets/jetbrainsmono-variable-font.woff2 +0 -0
- package/dist/assets/hydrogen/virtual-routes/assets/styles.css +238 -0
- package/dist/assets/hydrogen/virtual-routes/components/FlameChartWrapper.jsx +123 -0
- package/dist/assets/hydrogen/virtual-routes/components/HydrogenLogoBaseBW.jsx +32 -0
- package/dist/assets/hydrogen/virtual-routes/components/HydrogenLogoBaseColor.jsx +47 -0
- package/dist/assets/hydrogen/virtual-routes/components/IconBanner.jsx +292 -0
- package/dist/assets/hydrogen/virtual-routes/components/IconClose.jsx +38 -0
- package/dist/assets/hydrogen/virtual-routes/components/IconDiscard.jsx +44 -0
- package/dist/assets/hydrogen/virtual-routes/components/IconError.jsx +61 -0
- package/dist/assets/hydrogen/virtual-routes/components/IconGithub.jsx +23 -0
- package/dist/assets/hydrogen/virtual-routes/components/IconTwitter.jsx +21 -0
- package/dist/assets/hydrogen/virtual-routes/components/PageLayout.jsx +7 -0
- package/dist/assets/hydrogen/virtual-routes/components/RequestDetails.jsx +178 -0
- package/dist/assets/hydrogen/virtual-routes/components/RequestTable.jsx +91 -0
- package/dist/assets/hydrogen/virtual-routes/components/RequestWaterfall.jsx +151 -0
- package/dist/assets/hydrogen/virtual-routes/lib/useDebugNetworkServer.jsx +178 -0
- package/dist/assets/hydrogen/virtual-routes/routes/graphiql.jsx +5 -0
- package/dist/assets/hydrogen/virtual-routes/routes/index.jsx +265 -0
- package/dist/assets/hydrogen/virtual-routes/routes/subrequest-profiler.jsx +243 -0
- package/dist/assets/hydrogen/virtual-routes/virtual-root.jsx +64 -0
- package/dist/assets/hydrogen/vite/package.json +14 -0
- package/dist/assets/hydrogen/vite/vite.config.js +41 -0
- package/dist/chokidar-2CKIHN27.js +12 -0
- package/dist/chunk-EO6F7WJJ.js +2 -0
- package/dist/chunk-FB327AH7.js +5 -0
- package/dist/chunk-FJPX4XUR.js +2 -0
- package/dist/chunk-JKOXGRAA.js +10 -0
- package/dist/chunk-LNQWGFTB.js +45 -0
- package/dist/chunk-M6JXYI3V.js +23 -0
- package/dist/chunk-MNT4XW23.js +2 -0
- package/dist/chunk-N7HFZHSO.js +1145 -0
- package/dist/chunk-PMDMUCNY.js +2 -0
- package/dist/chunk-QGLB6FFL.js +3 -0
- package/dist/chunk-VMIOG46Y.js +2 -0
- package/dist/create-app.js +1867 -34
- package/dist/del-CZGKV5SQ.js +11 -0
- package/dist/devtools-ZCRGQE64.js +8 -0
- package/dist/error-handler-GEQXZJ25.js +2 -0
- package/dist/lib-NJYCLW6W.js +22 -0
- package/dist/morph-ZJCCGFNC.js +30499 -0
- package/dist/multipart-parser-6HGDQWV7.js +3 -0
- package/dist/open-OD6DRFEG.js +2 -0
- package/dist/out-7KAQXZLP.js +2 -0
- package/dist/yoga.wasm +0 -0
- package/package.json +7 -3
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import {type LoaderFunctionArgs} from '@shopify/remix-oxygen';
|
|
2
|
+
import {useRouteError, isRouteErrorResponse} from '@remix-run/react';
|
|
3
|
+
import {parseGid} from '@shopify/hydrogen';
|
|
4
|
+
|
|
5
|
+
export async function loader({request, context}: LoaderFunctionArgs) {
|
|
6
|
+
const url = new URL(request.url);
|
|
7
|
+
|
|
8
|
+
const {shop} = await context.storefront.query(ROBOTS_QUERY);
|
|
9
|
+
|
|
10
|
+
const shopId = parseGid(shop.id).id;
|
|
11
|
+
const body = robotsTxtData({url: url.origin, shopId});
|
|
12
|
+
|
|
13
|
+
return new Response(body, {
|
|
14
|
+
status: 200,
|
|
15
|
+
headers: {
|
|
16
|
+
'Content-Type': 'text/plain',
|
|
17
|
+
|
|
18
|
+
'Cache-Control': `max-age=${60 * 60 * 24}`,
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function robotsTxtData({url, shopId}: {shopId?: string; url?: string}) {
|
|
24
|
+
const sitemapUrl = url ? `${url}/sitemap.xml` : undefined;
|
|
25
|
+
|
|
26
|
+
return `
|
|
27
|
+
User-agent: *
|
|
28
|
+
${generalDisallowRules({sitemapUrl, shopId})}
|
|
29
|
+
|
|
30
|
+
# Google adsbot ignores robots.txt unless specifically named!
|
|
31
|
+
User-agent: adsbot-google
|
|
32
|
+
Disallow: /checkouts/
|
|
33
|
+
Disallow: /checkout
|
|
34
|
+
Disallow: /carts
|
|
35
|
+
Disallow: /orders
|
|
36
|
+
${shopId ? `Disallow: /${shopId}/checkouts` : ''}
|
|
37
|
+
${shopId ? `Disallow: /${shopId}/orders` : ''}
|
|
38
|
+
Disallow: /*?*oseid=*
|
|
39
|
+
Disallow: /*preview_theme_id*
|
|
40
|
+
Disallow: /*preview_script_id*
|
|
41
|
+
|
|
42
|
+
User-agent: Nutch
|
|
43
|
+
Disallow: /
|
|
44
|
+
|
|
45
|
+
User-agent: AhrefsBot
|
|
46
|
+
Crawl-delay: 10
|
|
47
|
+
${generalDisallowRules({sitemapUrl, shopId})}
|
|
48
|
+
|
|
49
|
+
User-agent: AhrefsSiteAudit
|
|
50
|
+
Crawl-delay: 10
|
|
51
|
+
${generalDisallowRules({sitemapUrl, shopId})}
|
|
52
|
+
|
|
53
|
+
User-agent: MJ12bot
|
|
54
|
+
Crawl-Delay: 10
|
|
55
|
+
|
|
56
|
+
User-agent: Pinterest
|
|
57
|
+
Crawl-delay: 1
|
|
58
|
+
`.trim();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* This function generates disallow rules that generally follow what Shopify's
|
|
63
|
+
* Online Store has as defaults for their robots.txt
|
|
64
|
+
*/
|
|
65
|
+
function generalDisallowRules({
|
|
66
|
+
shopId,
|
|
67
|
+
sitemapUrl,
|
|
68
|
+
}: {
|
|
69
|
+
shopId?: string;
|
|
70
|
+
sitemapUrl?: string;
|
|
71
|
+
}) {
|
|
72
|
+
return `Disallow: /admin
|
|
73
|
+
Disallow: /cart
|
|
74
|
+
Disallow: /orders
|
|
75
|
+
Disallow: /checkouts/
|
|
76
|
+
Disallow: /checkout
|
|
77
|
+
${shopId ? `Disallow: /${shopId}/checkouts` : ''}
|
|
78
|
+
${shopId ? `Disallow: /${shopId}/orders` : ''}
|
|
79
|
+
Disallow: /carts
|
|
80
|
+
Disallow: /account
|
|
81
|
+
Disallow: /collections/*sort_by*
|
|
82
|
+
Disallow: /*/collections/*sort_by*
|
|
83
|
+
Disallow: /collections/*+*
|
|
84
|
+
Disallow: /collections/*%2B*
|
|
85
|
+
Disallow: /collections/*%2b*
|
|
86
|
+
Disallow: /*/collections/*+*
|
|
87
|
+
Disallow: /*/collections/*%2B*
|
|
88
|
+
Disallow: /*/collections/*%2b*
|
|
89
|
+
Disallow: */collections/*filter*&*filter*
|
|
90
|
+
Disallow: /blogs/*+*
|
|
91
|
+
Disallow: /blogs/*%2B*
|
|
92
|
+
Disallow: /blogs/*%2b*
|
|
93
|
+
Disallow: /*/blogs/*+*
|
|
94
|
+
Disallow: /*/blogs/*%2B*
|
|
95
|
+
Disallow: /*/blogs/*%2b*
|
|
96
|
+
Disallow: /*?*oseid=*
|
|
97
|
+
Disallow: /*preview_theme_id*
|
|
98
|
+
Disallow: /*preview_script_id*
|
|
99
|
+
Disallow: /policies/
|
|
100
|
+
Disallow: /*/*?*ls=*&ls=*
|
|
101
|
+
Disallow: /*/*?*ls%3D*%3Fls%3D*
|
|
102
|
+
Disallow: /*/*?*ls%3d*%3fls%3d*
|
|
103
|
+
Disallow: /search
|
|
104
|
+
Allow: /search/
|
|
105
|
+
Disallow: /search/?*
|
|
106
|
+
Disallow: /apple-app-site-association
|
|
107
|
+
Disallow: /.well-known/shopify/monorail
|
|
108
|
+
${sitemapUrl ? `Sitemap: ${sitemapUrl}` : ''}`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const ROBOTS_QUERY = `#graphql
|
|
112
|
+
query StoreRobots($country: CountryCode, $language: LanguageCode)
|
|
113
|
+
@inContext(country: $country, language: $language) {
|
|
114
|
+
shop {
|
|
115
|
+
id
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
` as const;
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import {flattenConnection} from '@shopify/hydrogen';
|
|
2
|
+
import type {LoaderFunctionArgs} from '@shopify/remix-oxygen';
|
|
3
|
+
import type {SitemapQuery} from 'storefrontapi.generated';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* the google limit is 50K, however, the storefront API
|
|
7
|
+
* allows querying only 250 resources per pagination page
|
|
8
|
+
*/
|
|
9
|
+
const MAX_URLS = 250;
|
|
10
|
+
|
|
11
|
+
type Entry = {
|
|
12
|
+
url: string;
|
|
13
|
+
lastMod?: string;
|
|
14
|
+
changeFreq?: string;
|
|
15
|
+
image?: {
|
|
16
|
+
url: string;
|
|
17
|
+
title?: string;
|
|
18
|
+
caption?: string;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export async function loader({
|
|
23
|
+
request,
|
|
24
|
+
context: {storefront},
|
|
25
|
+
}: LoaderFunctionArgs) {
|
|
26
|
+
const data = await storefront.query(SITEMAP_QUERY, {
|
|
27
|
+
variables: {
|
|
28
|
+
urlLimits: MAX_URLS,
|
|
29
|
+
language: storefront.i18n.language,
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
if (!data) {
|
|
34
|
+
throw new Response('No data found', {status: 404});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const sitemap = generateSitemap({data, baseUrl: new URL(request.url).origin});
|
|
38
|
+
|
|
39
|
+
return new Response(sitemap, {
|
|
40
|
+
headers: {
|
|
41
|
+
'Content-Type': 'application/xml',
|
|
42
|
+
|
|
43
|
+
'Cache-Control': `max-age=${60 * 60 * 24}`,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function xmlEncode(string: string) {
|
|
49
|
+
return string.replace(/[&<>'"]/g, (char) => `&#${char.charCodeAt(0)};`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function generateSitemap({
|
|
53
|
+
data,
|
|
54
|
+
baseUrl,
|
|
55
|
+
}: {
|
|
56
|
+
data: SitemapQuery;
|
|
57
|
+
baseUrl: string;
|
|
58
|
+
}) {
|
|
59
|
+
const products = flattenConnection(data.products)
|
|
60
|
+
.filter((product) => product.onlineStoreUrl)
|
|
61
|
+
.map((product) => {
|
|
62
|
+
const url = `${baseUrl}/products/${xmlEncode(product.handle)}`;
|
|
63
|
+
|
|
64
|
+
const productEntry: Entry = {
|
|
65
|
+
url,
|
|
66
|
+
lastMod: product.updatedAt,
|
|
67
|
+
changeFreq: 'daily',
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
if (product.featuredImage?.url) {
|
|
71
|
+
productEntry.image = {
|
|
72
|
+
url: xmlEncode(product.featuredImage.url),
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
if (product.title) {
|
|
76
|
+
productEntry.image.title = xmlEncode(product.title);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (product.featuredImage.altText) {
|
|
80
|
+
productEntry.image.caption = xmlEncode(product.featuredImage.altText);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return productEntry;
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const collections = flattenConnection(data.collections)
|
|
88
|
+
.filter((collection) => collection.onlineStoreUrl)
|
|
89
|
+
.map((collection) => {
|
|
90
|
+
const url = `${baseUrl}/collections/${collection.handle}`;
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
url,
|
|
94
|
+
lastMod: collection.updatedAt,
|
|
95
|
+
changeFreq: 'daily',
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const pages = flattenConnection(data.pages)
|
|
100
|
+
.filter((page) => page.onlineStoreUrl)
|
|
101
|
+
.map((page) => {
|
|
102
|
+
const url = `${baseUrl}/pages/${page.handle}`;
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
url,
|
|
106
|
+
lastMod: page.updatedAt,
|
|
107
|
+
changeFreq: 'weekly',
|
|
108
|
+
};
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const urls = [...products, ...collections, ...pages];
|
|
112
|
+
|
|
113
|
+
return `
|
|
114
|
+
<urlset
|
|
115
|
+
xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
|
|
116
|
+
xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"
|
|
117
|
+
>
|
|
118
|
+
${urls.map(renderUrlTag).join('')}
|
|
119
|
+
</urlset>`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function renderUrlTag({url, lastMod, changeFreq, image}: Entry) {
|
|
123
|
+
const imageTag = image
|
|
124
|
+
? `<image:image>
|
|
125
|
+
<image:loc>${image.url}</image:loc>
|
|
126
|
+
<image:title>${image.title ?? ''}</image:title>
|
|
127
|
+
<image:caption>${image.caption ?? ''}</image:caption>
|
|
128
|
+
</image:image>`.trim()
|
|
129
|
+
: '';
|
|
130
|
+
|
|
131
|
+
return `
|
|
132
|
+
<url>
|
|
133
|
+
<loc>${url}</loc>
|
|
134
|
+
<lastmod>${lastMod}</lastmod>
|
|
135
|
+
<changefreq>${changeFreq}</changefreq>
|
|
136
|
+
${imageTag}
|
|
137
|
+
</url>
|
|
138
|
+
`.trim();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const SITEMAP_QUERY = `#graphql
|
|
142
|
+
query Sitemap($urlLimits: Int, $language: LanguageCode)
|
|
143
|
+
@inContext(language: $language) {
|
|
144
|
+
products(
|
|
145
|
+
first: $urlLimits
|
|
146
|
+
query: "published_status:'online_store:visible'"
|
|
147
|
+
) {
|
|
148
|
+
nodes {
|
|
149
|
+
updatedAt
|
|
150
|
+
handle
|
|
151
|
+
onlineStoreUrl
|
|
152
|
+
title
|
|
153
|
+
featuredImage {
|
|
154
|
+
url
|
|
155
|
+
altText
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
collections(
|
|
160
|
+
first: $urlLimits
|
|
161
|
+
query: "published_status:'online_store:visible'"
|
|
162
|
+
) {
|
|
163
|
+
nodes {
|
|
164
|
+
updatedAt
|
|
165
|
+
handle
|
|
166
|
+
onlineStoreUrl
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
pages(first: $urlLimits, query: "published_status:'published'") {
|
|
170
|
+
nodes {
|
|
171
|
+
updatedAt
|
|
172
|
+
handle
|
|
173
|
+
onlineStoreUrl
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
` as const;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import {defer, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
|
|
2
|
+
import {Await, useLoaderData, Link, type MetaFunction} from '@remix-run/react';
|
|
3
|
+
import {Suspense} from 'react';
|
|
4
|
+
import {Image, Money} from '@shopify/hydrogen';
|
|
5
|
+
import type {
|
|
6
|
+
FeaturedCollectionFragment,
|
|
7
|
+
RecommendedProductsQuery,
|
|
8
|
+
} from 'storefrontapi.generated';
|
|
9
|
+
|
|
10
|
+
export const meta: MetaFunction = () => {
|
|
11
|
+
return [{title: 'Hydrogen | Home'}];
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export async function loader(args: LoaderFunctionArgs) {
|
|
15
|
+
// Start fetching non-critical data without blocking time to first byte
|
|
16
|
+
const deferredData = loadDeferredData(args);
|
|
17
|
+
|
|
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
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export default function Homepage() {
|
|
59
|
+
const data = useLoaderData<typeof loader>();
|
|
60
|
+
return (
|
|
61
|
+
<div className="home">
|
|
62
|
+
<FeaturedCollection collection={data.featuredCollection} />
|
|
63
|
+
<RecommendedProducts products={data.recommendedProducts} />
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function FeaturedCollection({
|
|
69
|
+
collection,
|
|
70
|
+
}: {
|
|
71
|
+
collection: FeaturedCollectionFragment;
|
|
72
|
+
}) {
|
|
73
|
+
if (!collection) return null;
|
|
74
|
+
const image = collection?.image;
|
|
75
|
+
return (
|
|
76
|
+
<Link
|
|
77
|
+
className="featured-collection"
|
|
78
|
+
to={`/collections/${collection.handle}`}
|
|
79
|
+
>
|
|
80
|
+
{image && (
|
|
81
|
+
<div className="featured-collection-image">
|
|
82
|
+
<Image data={image} sizes="100vw" />
|
|
83
|
+
</div>
|
|
84
|
+
)}
|
|
85
|
+
<h1>{collection.title}</h1>
|
|
86
|
+
</Link>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function RecommendedProducts({
|
|
91
|
+
products,
|
|
92
|
+
}: {
|
|
93
|
+
products: Promise<RecommendedProductsQuery | null>;
|
|
94
|
+
}) {
|
|
95
|
+
return (
|
|
96
|
+
<div className="recommended-products">
|
|
97
|
+
<h2>Recommended Products</h2>
|
|
98
|
+
<Suspense fallback={<div>Loading...</div>}>
|
|
99
|
+
<Await resolve={products}>
|
|
100
|
+
{(response) => (
|
|
101
|
+
<div className="recommended-products-grid">
|
|
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}
|
|
121
|
+
</div>
|
|
122
|
+
)}
|
|
123
|
+
</Await>
|
|
124
|
+
</Suspense>
|
|
125
|
+
<br />
|
|
126
|
+
</div>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const FEATURED_COLLECTION_QUERY = `#graphql
|
|
131
|
+
fragment FeaturedCollection on Collection {
|
|
132
|
+
id
|
|
133
|
+
title
|
|
134
|
+
image {
|
|
135
|
+
id
|
|
136
|
+
url
|
|
137
|
+
altText
|
|
138
|
+
width
|
|
139
|
+
height
|
|
140
|
+
}
|
|
141
|
+
handle
|
|
142
|
+
}
|
|
143
|
+
query FeaturedCollection($country: CountryCode, $language: LanguageCode)
|
|
144
|
+
@inContext(country: $country, language: $language) {
|
|
145
|
+
collections(first: 1, sortKey: UPDATED_AT, reverse: true) {
|
|
146
|
+
nodes {
|
|
147
|
+
...FeaturedCollection
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
` as const;
|
|
152
|
+
|
|
153
|
+
const RECOMMENDED_PRODUCTS_QUERY = `#graphql
|
|
154
|
+
fragment RecommendedProduct on Product {
|
|
155
|
+
id
|
|
156
|
+
title
|
|
157
|
+
handle
|
|
158
|
+
priceRange {
|
|
159
|
+
minVariantPrice {
|
|
160
|
+
amount
|
|
161
|
+
currencyCode
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
images(first: 1) {
|
|
165
|
+
nodes {
|
|
166
|
+
id
|
|
167
|
+
url
|
|
168
|
+
altText
|
|
169
|
+
width
|
|
170
|
+
height
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
query RecommendedProducts ($country: CountryCode, $language: LanguageCode)
|
|
175
|
+
@inContext(country: $country, language: $language) {
|
|
176
|
+
products(first: 4, sortKey: UPDATED_AT, reverse: true) {
|
|
177
|
+
nodes {
|
|
178
|
+
...RecommendedProduct
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
` as const;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import {redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
|
|
2
|
+
|
|
3
|
+
// fallback wild card for all unauthenticated routes in account section
|
|
4
|
+
export async function loader({context}: LoaderFunctionArgs) {
|
|
5
|
+
await context.customerAccount.handleAuthStatus();
|
|
6
|
+
|
|
7
|
+
return redirect('/account');
|
|
8
|
+
}
|