-
-
+const carouselItems = [
+
+
+
Slide 1
+
Welcome to the carousel
- );
-}
+
,
+
+
+
Slide 2
+
Navigate with arrows or dots
+
+
,
+
+
+
Slide 3
+
Smooth transitions included
+
+
,
+
+
+
Slide 4
+
Click any dot to jump to a slide
+
+
,
+];
-function FeaturedCollection({
- collection,
-}: {
- collection: FeaturedCollectionFragment;
-}) {
- if (!collection) return null;
- const image = collection?.image;
+export default function Homepage() {
return (
-
- {image && (
-
-
+
+
+
+
+
Updates
+
+
+
+
+
+
+
+
);
}
-
-const FEATURED_COLLECTION_QUERY = `#graphql
- fragment FeaturedCollection on Collection {
- id
- title
- image {
- id
- url
- altText
- width
- height
- }
- handle
- }
- query FeaturedCollection($country: CountryCode, $language: LanguageCode)
- @inContext(country: $country, language: $language) {
- collections(first: 1, sortKey: UPDATED_AT, reverse: true) {
- nodes {
- ...FeaturedCollection
- }
- }
- }
-` as const;
-
-const RECOMMENDED_PRODUCTS_QUERY = `#graphql
- fragment RecommendedProduct on Product {
- id
- title
- handle
- priceRange {
- minVariantPrice {
- amount
- currencyCode
- }
- }
- images(first: 1) {
- nodes {
- id
- url
- altText
- width
- height
- }
- }
- productType
- }
- query RecommendedProducts ($country: CountryCode, $language: LanguageCode)
- @inContext(country: $country, language: $language) {
- products(first: 250, sortKey: UPDATED_AT, reverse: true) {
- nodes {
- ...RecommendedProduct
- }
- }
- }
-` as const;
diff --git a/app/routes/_index_old.tsx b/app/routes/_index_old.tsx
new file mode 100644
index 0000000..7d86765
--- /dev/null
+++ b/app/routes/_index_old.tsx
@@ -0,0 +1,183 @@
+import {type LoaderFunctionArgs} from '@shopify/remix-oxygen';
+import {Await, useLoaderData, Link, type MetaFunction} from '@remix-run/react';
+import {Suspense} from 'react';
+import {Image, Money} from '@shopify/hydrogen';
+import type {
+ FeaturedCollectionFragment,
+ RecommendedProductsQuery,
+} from 'storefrontapi.generated';
+
+export const meta: MetaFunction = () => {
+ return [{title: 'Hydrogen | Home'}];
+};
+
+export async function loader(args: LoaderFunctionArgs) {
+ // Start fetching non-critical data without blocking time to first byte
+ const deferredData = loadDeferredData(args);
+
+ // Await the critical data required to render initial state of the page
+ const criticalData = await loadCriticalData(args);
+
+ return {...deferredData, ...criticalData};
+}
+
+/**
+ * Load data necessary for rendering content above the fold. This is the critical data
+ * needed to render the page. If it's unavailable, the whole page should 400 or 500 error.
+ */
+async function loadCriticalData({context}: LoaderFunctionArgs) {
+ const [{collections}] = await Promise.all([
+ context.storefront.query(FEATURED_COLLECTION_QUERY),
+ // Add other queries here, so that they are loaded in parallel
+ ]);
+
+ return {
+ featuredCollection: collections.nodes[0],
+ };
+}
+
+/**
+ * Load data for rendering content below the fold. This data is deferred and will be
+ * fetched after the initial page load. If it's unavailable, the page should still 200.
+ * Make sure to not throw any errors here, as it will cause the page to 500.
+ */
+function loadDeferredData({context}: LoaderFunctionArgs) {
+ const recommendedProducts = context.storefront
+ .query(RECOMMENDED_PRODUCTS_QUERY)
+ .catch((error) => {
+ // Log query errors, but don't throw them so the page can still render
+ console.error(error);
+ return null;
+ });
+
+ return {
+ recommendedProducts,
+ };
+}
+
+export default function Homepage() {
+ const data = useLoaderData
();
+ return (
+
+
+
+
+ );
+}
+
+function FeaturedCollection({
+ collection,
+}: {
+ collection: FeaturedCollectionFragment;
+}) {
+ if (!collection) return null;
+ const image = collection?.image;
+ return (
+
+ {image && (
+
+
+
+ )}
+ {collection.title}
+
+ );
+}
+
+function RecommendedProducts({
+ products,
+}: {
+ products: Promise;
+}) {
+ return (
+
+
Recommended Products
+ Loading...}>
+
+ {(response) => (
+
+ {response
+ ? response.products.nodes.map((product) => (
+
+
+
{product.title}
+
+
+
+
+ ))
+ : null}
+
+ )}
+
+
+
+
+ );
+}
+
+const FEATURED_COLLECTION_QUERY = `#graphql
+ fragment FeaturedCollection on Collection {
+ id
+ title
+ image {
+ id
+ url
+ altText
+ width
+ height
+ }
+ handle
+ }
+ query FeaturedCollection($country: CountryCode, $language: LanguageCode)
+ @inContext(country: $country, language: $language) {
+ collections(first: 1, sortKey: UPDATED_AT, reverse: true) {
+ nodes {
+ ...FeaturedCollection
+ }
+ }
+ }
+` as const;
+
+const RECOMMENDED_PRODUCTS_QUERY = `#graphql
+ fragment RecommendedProduct on Product {
+ id
+ title
+ handle
+ priceRange {
+ minVariantPrice {
+ amount
+ currencyCode
+ }
+ }
+ images(first: 1) {
+ nodes {
+ id
+ url
+ altText
+ width
+ height
+ }
+ }
+ productType
+ }
+ query RecommendedProducts ($country: CountryCode, $language: LanguageCode)
+ @inContext(country: $country, language: $language) {
+ products(first: 250, sortKey: UPDATED_AT, reverse: true) {
+ nodes {
+ ...RecommendedProduct
+ }
+ }
+ }
+` as const;
diff --git a/package-lock.json b/package-lock.json
index 8b40efc..1872fad 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -17,6 +17,7 @@
"@tailwindcss/typography": "^0.5.16",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
+ "embla-carousel-react": "^8.6.0",
"flowbite-react": "^0.12.5",
"graphql": "^16.6.0",
"graphql-tag": "^2.12.6",
@@ -9069,6 +9070,34 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/embla-carousel": {
+ "version": "8.6.0",
+ "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz",
+ "integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==",
+ "license": "MIT"
+ },
+ "node_modules/embla-carousel-react": {
+ "version": "8.6.0",
+ "resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-8.6.0.tgz",
+ "integrity": "sha512-0/PjqU7geVmo6F734pmPqpyHqiM99olvyecY7zdweCw+6tKEXnrE90pBiBbMMU8s5tICemzpQ3hi5EpxzGW+JA==",
+ "license": "MIT",
+ "dependencies": {
+ "embla-carousel": "8.6.0",
+ "embla-carousel-reactive-utils": "8.6.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/embla-carousel-reactive-utils": {
+ "version": "8.6.0",
+ "resolved": "https://registry.npmjs.org/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-8.6.0.tgz",
+ "integrity": "sha512-fMVUDUEx0/uIEDM0Mz3dHznDhfX+znCCDCeIophYb1QGVM7YThSWX+wz11zlYwWFOr74b4QLGg0hrGPJeG2s4A==",
+ "license": "MIT",
+ "peerDependencies": {
+ "embla-carousel": "8.6.0"
+ }
+ },
"node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
diff --git a/package.json b/package.json
index 9718d61..4f6505c 100644
--- a/package.json
+++ b/package.json
@@ -28,6 +28,7 @@
"@tailwindcss/typography": "^0.5.16",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
+ "embla-carousel-react": "^8.6.0",
"flowbite-react": "^0.12.5",
"graphql": "^16.6.0",
"graphql-tag": "^2.12.6",
@@ -88,5 +89,7 @@
"engines": {
"node": ">=18.0.0"
},
- "browserslist": ["defaults"]
+ "browserslist": [
+ "defaults"
+ ]
}