From e829bd81098840298fa7f5c99cf1fe2b8f1f029d Mon Sep 17 00:00:00 2001 From: malewis5 <29663600+malewis5@users.noreply.github.com> Date: Wed, 17 Jun 2026 18:47:25 -0400 Subject: [PATCH 1/4] Hoist nextjs example lib/ and components/ out of app/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the Next.js example's components/ and lib/ directories from under app/ to the project root, and replace relative imports with the @/ path alias. No behavior change — imports only, plus pure file moves. Verified: tsc --noEmit and next build both pass. Co-Authored-By: Claude Opus 4.8 (1M context) --- examples/nextjs/README.md | 8 ++++---- examples/nextjs/app/blogs/news/[handle]/page.tsx | 2 +- examples/nextjs/app/blogs/news/page.tsx | 2 +- examples/nextjs/app/cart/page.tsx | 2 +- examples/nextjs/app/collections/[handle]/page.tsx | 10 +++++----- examples/nextjs/app/collections/page.tsx | 2 +- examples/nextjs/app/layout.tsx | 14 +++++++------- examples/nextjs/app/not-found.tsx | 2 +- examples/nextjs/app/page.tsx | 4 ++-- examples/nextjs/app/products/[handle]/page.tsx | 4 ++-- examples/nextjs/app/search/page.tsx | 8 ++++---- .../{app => }/components/AnalyticsTracker.tsx | 2 +- examples/nextjs/{app => }/components/Cart.tsx | 4 ++-- .../nextjs/{app => }/components/CartDrawer.tsx | 5 +++-- .../{app => }/components/CollectionBrowser.tsx | 5 +++-- .../components/CollectionViewedTracker.tsx | 2 +- examples/nextjs/{app => }/components/Footer.tsx | 0 examples/nextjs/{app => }/components/Header.tsx | 4 ++-- .../nextjs/{app => }/components/ProductCard.tsx | 2 +- .../nextjs/{app => }/components/ProductDetails.tsx | 7 ++++--- .../{app => }/components/ProductViewedTracker.tsx | 2 +- examples/nextjs/{app => }/components/Providers.tsx | 2 +- examples/nextjs/{app => }/lib/analytics.ts | 0 examples/nextjs/{app => }/lib/cart-drawer.ts | 0 examples/nextjs/{app => }/lib/cart-handlers.ts | 0 examples/nextjs/{app => }/lib/cart.ts | 0 examples/nextjs/{app => }/lib/collection.ts | 2 +- examples/nextjs/{app => }/lib/money.ts | 0 examples/nextjs/{app => }/lib/search.ts | 2 +- examples/nextjs/{app => }/lib/storefront.ts | 0 examples/nextjs/{app => }/lib/url.ts | 0 examples/nextjs/proxy.ts | 2 +- 32 files changed, 51 insertions(+), 48 deletions(-) rename examples/nextjs/{app => }/components/AnalyticsTracker.tsx (87%) rename examples/nextjs/{app => }/components/Cart.tsx (99%) rename examples/nextjs/{app => }/components/CartDrawer.tsx (97%) rename examples/nextjs/{app => }/components/CollectionBrowser.tsx (99%) rename examples/nextjs/{app => }/components/CollectionViewedTracker.tsx (86%) rename examples/nextjs/{app => }/components/Footer.tsx (100%) rename examples/nextjs/{app => }/components/Header.tsx (98%) rename examples/nextjs/{app => }/components/ProductCard.tsx (96%) rename examples/nextjs/{app => }/components/ProductDetails.tsx (98%) rename examples/nextjs/{app => }/components/ProductViewedTracker.tsx (94%) rename examples/nextjs/{app => }/components/Providers.tsx (86%) rename examples/nextjs/{app => }/lib/analytics.ts (100%) rename examples/nextjs/{app => }/lib/cart-drawer.ts (100%) rename examples/nextjs/{app => }/lib/cart-handlers.ts (100%) rename examples/nextjs/{app => }/lib/cart.ts (100%) rename examples/nextjs/{app => }/lib/collection.ts (97%) rename examples/nextjs/{app => }/lib/money.ts (100%) rename examples/nextjs/{app => }/lib/search.ts (98%) rename examples/nextjs/{app => }/lib/storefront.ts (100%) rename examples/nextjs/{app => }/lib/url.ts (100%) diff --git a/examples/nextjs/README.md b/examples/nextjs/README.md index 37d12f9a55..abc2aa171d 100644 --- a/examples/nextjs/README.md +++ b/examples/nextjs/README.md @@ -32,7 +32,7 @@ The scaffold ships an `AGENTS.md` (and `CLAUDE.md` pointer to it) that tells cod ## Stubbed vs. live - **Live**: product data, collection data, article content, prices, images, options, cart state (server-fetched via `createCartServerHandlers().get` in the root layout and hydrated into `CartProvider`), cart drawer, cart page mutations, product variant selection, add-to-cart, checkout, Shop Pay. -- **Stubbed**: hero links, search, account, newsletter form, news index list, color swatch hex values (mapped client-side from option name → CSS color in `app/components/ProductDetails.tsx`). +- **Stubbed**: hero links, search, account, newsletter form, news index list, color swatch hex values (mapped client-side from option name → CSS color in `components/ProductDetails.tsx`). ## Run @@ -50,10 +50,10 @@ Note: Next.js 16 does **not** cache `fetch` by default — every request to a se Same shape as the React Router example — every framework port re-invents the same three pieces, so this is the feedback loop into the Hydrogen package in `packages/hydrogen`: -- `storefrontClient.graphql()` in `app/lib/storefront.ts` — now uses `createStorefrontClient` from `@shopify/hydrogen` with `gql.tada` for zero-config type inference. Error normalization and request-id propagation are handled by the core client. -- `formatMoney()` in `app/lib/money.ts` — every example needs money formatting from a `MoneyV2`-shaped object. +- `storefrontClient.graphql()` in `lib/storefront.ts` — now uses `createStorefrontClient` from `@shopify/hydrogen` with `gql.tada` for zero-config type inference. Error normalization and request-id propagation are handled by the core client. +- `formatMoney()` in `lib/money.ts` — every example needs money formatting from a `MoneyV2`-shaped object. - `ProductCard` reads a narrow product shape (`handle`, `title`, `featuredImage`, `priceRange.minVariantPrice`). A core fragment + type for "product card" would let routes share GraphQL fragments instead of re-listing fields. -- Color swatch mapping (`SWATCHES` in `app/components/ProductDetails.tsx`) is hand-rolled — option-value → swatch metadata is a real merchant problem; the SDK should have an opinion on how to expose it. +- Color swatch mapping (`SWATCHES` in `components/ProductDetails.tsx`) is hand-rolled — option-value → swatch metadata is a real merchant problem; the SDK should have an opinion on how to expose it. ## Open questions diff --git a/examples/nextjs/app/blogs/news/[handle]/page.tsx b/examples/nextjs/app/blogs/news/[handle]/page.tsx index 925bce64c8..739b3812f9 100644 --- a/examples/nextjs/app/blogs/news/[handle]/page.tsx +++ b/examples/nextjs/app/blogs/news/[handle]/page.tsx @@ -3,7 +3,7 @@ import type { Metadata } from "next"; import Link from "next/link"; import { notFound } from "next/navigation"; -import { getStorefrontClient } from "../../../lib/storefront"; +import { getStorefrontClient } from "@/lib/storefront"; const ARTICLE_QUERY = gql(` query Article($handle: String!) { diff --git a/examples/nextjs/app/blogs/news/page.tsx b/examples/nextjs/app/blogs/news/page.tsx index 0a2001e6da..4ed8ed7f0c 100644 --- a/examples/nextjs/app/blogs/news/page.tsx +++ b/examples/nextjs/app/blogs/news/page.tsx @@ -2,7 +2,7 @@ import { gql } from "@shopify/hydrogen"; import Link from "next/link"; import { notFound } from "next/navigation"; -import { getStorefrontClient } from "../../lib/storefront"; +import { getStorefrontClient } from "@/lib/storefront"; const NEWS_QUERY = gql(` query News { diff --git a/examples/nextjs/app/cart/page.tsx b/examples/nextjs/app/cart/page.tsx index 8574ead590..e915767423 100644 --- a/examples/nextjs/app/cart/page.tsx +++ b/examples/nextjs/app/cart/page.tsx @@ -1,6 +1,6 @@ import type { Metadata } from "next"; -import { CartContent } from "../components/Cart"; +import { CartContent } from "@/components/Cart"; export const metadata: Metadata = { title: "Cart — Mock.shop", diff --git a/examples/nextjs/app/collections/[handle]/page.tsx b/examples/nextjs/app/collections/[handle]/page.tsx index 0b745f70bc..da19d6d21b 100644 --- a/examples/nextjs/app/collections/[handle]/page.tsx +++ b/examples/nextjs/app/collections/[handle]/page.tsx @@ -1,11 +1,11 @@ import type { Metadata } from "next"; import { notFound } from "next/navigation"; -import { CollectionBrowser } from "../../components/CollectionBrowser"; -import { CollectionViewedTracker } from "../../components/CollectionViewedTracker"; -import { queryCollection } from "../../lib/collection"; -import { getStorefrontClient } from "../../lib/storefront"; -import { pageSearchParamsToUrlSearchParams, type PageSearchParams } from "../../lib/url"; +import { CollectionBrowser } from "@/components/CollectionBrowser"; +import { CollectionViewedTracker } from "@/components/CollectionViewedTracker"; +import { queryCollection } from "@/lib/collection"; +import { getStorefrontClient } from "@/lib/storefront"; +import { pageSearchParamsToUrlSearchParams, type PageSearchParams } from "@/lib/url"; type Props = { params: Promise<{ handle: string }>; diff --git a/examples/nextjs/app/collections/page.tsx b/examples/nextjs/app/collections/page.tsx index 597c5b8059..d08ba05276 100644 --- a/examples/nextjs/app/collections/page.tsx +++ b/examples/nextjs/app/collections/page.tsx @@ -1,7 +1,7 @@ import { gql } from "@shopify/hydrogen"; import Link from "next/link"; -import { getStorefrontClient } from "../lib/storefront"; +import { getStorefrontClient } from "@/lib/storefront"; const COLLECTIONS_QUERY = gql(` query Collections { diff --git a/examples/nextjs/app/layout.tsx b/examples/nextjs/app/layout.tsx index 4dabc92426..22049fa9ec 100644 --- a/examples/nextjs/app/layout.tsx +++ b/examples/nextjs/app/layout.tsx @@ -6,13 +6,13 @@ import { Inter } from "next/font/google"; import Script from "next/script"; import { Suspense } from "react"; -import { AnalyticsTracker } from "./components/AnalyticsTracker"; -import { CartDrawer } from "./components/CartDrawer"; -import { Footer } from "./components/Footer"; -import { Header } from "./components/Header"; -import { Providers } from "./components/Providers"; -import { cartHandlers } from "./lib/cart-handlers"; -import { getStorefrontClient } from "./lib/storefront"; +import { AnalyticsTracker } from "@/components/AnalyticsTracker"; +import { CartDrawer } from "@/components/CartDrawer"; +import { Footer } from "@/components/Footer"; +import { Header } from "@/components/Header"; +import { Providers } from "@/components/Providers"; +import { cartHandlers } from "@/lib/cart-handlers"; +import { getStorefrontClient } from "@/lib/storefront"; const inter = Inter({ subsets: ["latin"], diff --git a/examples/nextjs/app/not-found.tsx b/examples/nextjs/app/not-found.tsx index 0f883c75c5..3431d3f8e0 100644 --- a/examples/nextjs/app/not-found.tsx +++ b/examples/nextjs/app/not-found.tsx @@ -3,7 +3,7 @@ import { headers } from "next/headers"; import Link from "next/link"; import { redirect } from "next/navigation"; -import { getStorefrontClient } from "./lib/storefront"; +import { getStorefrontClient } from "@/lib/storefront"; // Reading headers() and possibly redirecting must happen per-request. export const dynamic = "force-dynamic"; diff --git a/examples/nextjs/app/page.tsx b/examples/nextjs/app/page.tsx index e4e41937da..3378910656 100644 --- a/examples/nextjs/app/page.tsx +++ b/examples/nextjs/app/page.tsx @@ -1,8 +1,8 @@ import { gql } from "@shopify/hydrogen"; import Link from "next/link"; -import { ProductCard } from "./components/ProductCard"; -import { getStorefrontClient } from "./lib/storefront"; +import { ProductCard } from "@/components/ProductCard"; +import { getStorefrontClient } from "@/lib/storefront"; const HOME_QUERY = gql(` query Home { diff --git a/examples/nextjs/app/products/[handle]/page.tsx b/examples/nextjs/app/products/[handle]/page.tsx index 079a1b3c40..1bf491444e 100644 --- a/examples/nextjs/app/products/[handle]/page.tsx +++ b/examples/nextjs/app/products/[handle]/page.tsx @@ -2,8 +2,8 @@ import { getSelectedProductOptions, gql, type SelectedOption } from "@shopify/hy import type { Metadata } from "next"; import { notFound } from "next/navigation"; -import { ProductDetails } from "../../components/ProductDetails"; -import { getStorefrontClient } from "../../lib/storefront"; +import { ProductDetails } from "@/components/ProductDetails"; +import { getStorefrontClient } from "@/lib/storefront"; const PRODUCT_VARIANT_FRAGMENT = gql(` fragment ProductVariantFragment on ProductVariant { diff --git a/examples/nextjs/app/search/page.tsx b/examples/nextjs/app/search/page.tsx index 86c61cd80e..2dd636d7d9 100644 --- a/examples/nextjs/app/search/page.tsx +++ b/examples/nextjs/app/search/page.tsx @@ -1,9 +1,9 @@ import type { Metadata } from "next"; -import { CollectionBrowser } from "../components/CollectionBrowser"; -import { querySearch } from "../lib/search"; -import { getStorefrontClient } from "../lib/storefront"; -import { pageSearchParamsToUrlSearchParams, type PageSearchParams } from "../lib/url"; +import { CollectionBrowser } from "@/components/CollectionBrowser"; +import { querySearch } from "@/lib/search"; +import { getStorefrontClient } from "@/lib/storefront"; +import { pageSearchParamsToUrlSearchParams, type PageSearchParams } from "@/lib/url"; type Props = { searchParams: PageSearchParams; diff --git a/examples/nextjs/app/components/AnalyticsTracker.tsx b/examples/nextjs/components/AnalyticsTracker.tsx similarity index 87% rename from examples/nextjs/app/components/AnalyticsTracker.tsx rename to examples/nextjs/components/AnalyticsTracker.tsx index 9143376d28..2ce1a6f4b9 100644 --- a/examples/nextjs/app/components/AnalyticsTracker.tsx +++ b/examples/nextjs/components/AnalyticsTracker.tsx @@ -3,7 +3,7 @@ import { usePathname, useSearchParams } from "next/navigation"; import { useEffect } from "react"; -import { getAnalytics, analyticsShop, AnalyticsEvent } from "../lib/analytics"; +import { getAnalytics, analyticsShop, AnalyticsEvent } from "@/lib/analytics"; export function AnalyticsTracker() { const pathname = usePathname(); diff --git a/examples/nextjs/app/components/Cart.tsx b/examples/nextjs/components/Cart.tsx similarity index 99% rename from examples/nextjs/app/components/Cart.tsx rename to examples/nextjs/components/Cart.tsx index 4cceebae2f..ad360f2524 100644 --- a/examples/nextjs/app/components/Cart.tsx +++ b/examples/nextjs/components/Cart.tsx @@ -3,8 +3,8 @@ import { ShopPayButton as HydrogenShopPayButton } from "@shopify/hydrogen/react"; import { useEffect, useMemo, useState } from "react"; -import { useCart, useCartForm } from "../lib/cart"; -import { formatMoney } from "../lib/money"; +import { useCart, useCartForm } from "@/lib/cart"; +import { formatMoney } from "@/lib/money"; function AddTestItem() { return ( diff --git a/examples/nextjs/app/components/CartDrawer.tsx b/examples/nextjs/components/CartDrawer.tsx similarity index 97% rename from examples/nextjs/app/components/CartDrawer.tsx rename to examples/nextjs/components/CartDrawer.tsx index e322a3153d..67229d473c 100644 --- a/examples/nextjs/app/components/CartDrawer.tsx +++ b/examples/nextjs/components/CartDrawer.tsx @@ -2,13 +2,14 @@ import { useEffect, useMemo } from "react"; -import { useCart } from "../lib/cart"; +import { useCart } from "@/lib/cart"; import { CART_DRAWER_ID, closeCartDrawer, configureOpenCartAction, supportsDialogCommands, -} from "../lib/cart-drawer"; +} from "@/lib/cart-drawer"; + import { CartNote, CartTotals, CheckoutButton, DiscountCodes, LineItems } from "./Cart"; // React types do not include Invoker Commands yet: https://github.com/facebook/react/issues/32478 diff --git a/examples/nextjs/app/components/CollectionBrowser.tsx b/examples/nextjs/components/CollectionBrowser.tsx similarity index 99% rename from examples/nextjs/app/components/CollectionBrowser.tsx rename to examples/nextjs/components/CollectionBrowser.tsx index a5a1da6be9..4b30e1b341 100644 --- a/examples/nextjs/app/components/CollectionBrowser.tsx +++ b/examples/nextjs/components/CollectionBrowser.tsx @@ -12,8 +12,9 @@ import Link from "next/link"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; import { useEffect } from "react"; -import { AnalyticsEvent, analyticsShop, getAnalytics } from "../lib/analytics"; -import { formatMoney } from "../lib/money"; +import { AnalyticsEvent, analyticsShop, getAnalytics } from "@/lib/analytics"; +import { formatMoney } from "@/lib/money"; + import { ProductCard, type ProductCardData } from "./ProductCard"; const COLLECTION_SORT_OPTIONS = [ diff --git a/examples/nextjs/app/components/CollectionViewedTracker.tsx b/examples/nextjs/components/CollectionViewedTracker.tsx similarity index 86% rename from examples/nextjs/app/components/CollectionViewedTracker.tsx rename to examples/nextjs/components/CollectionViewedTracker.tsx index 35e5808b1e..0a788b4e49 100644 --- a/examples/nextjs/app/components/CollectionViewedTracker.tsx +++ b/examples/nextjs/components/CollectionViewedTracker.tsx @@ -2,7 +2,7 @@ import { useEffect } from "react"; -import { getAnalytics, analyticsShop, AnalyticsEvent } from "../lib/analytics"; +import { getAnalytics, analyticsShop, AnalyticsEvent } from "@/lib/analytics"; type Props = { collection: { id: string; handle: string }; diff --git a/examples/nextjs/app/components/Footer.tsx b/examples/nextjs/components/Footer.tsx similarity index 100% rename from examples/nextjs/app/components/Footer.tsx rename to examples/nextjs/components/Footer.tsx diff --git a/examples/nextjs/app/components/Header.tsx b/examples/nextjs/components/Header.tsx similarity index 98% rename from examples/nextjs/app/components/Header.tsx rename to examples/nextjs/components/Header.tsx index 02a7be238d..ae79644f50 100644 --- a/examples/nextjs/app/components/Header.tsx +++ b/examples/nextjs/components/Header.tsx @@ -4,8 +4,8 @@ import type { HeaderCollection } from "@shared/header"; import Link from "next/link"; import { useEffect, useState } from "react"; -import { useCart } from "../lib/cart"; -import { CART_DRAWER_ID, openCartDrawer, supportsDialogCommands } from "../lib/cart-drawer"; +import { useCart } from "@/lib/cart"; +import { CART_DRAWER_ID, openCartDrawer, supportsDialogCommands } from "@/lib/cart-drawer"; // React types do not include Invoker Commands yet: https://github.com/facebook/react/issues/32478 const openCartCommandAttributes = { diff --git a/examples/nextjs/app/components/ProductCard.tsx b/examples/nextjs/components/ProductCard.tsx similarity index 96% rename from examples/nextjs/app/components/ProductCard.tsx rename to examples/nextjs/components/ProductCard.tsx index 0cc5eec470..d000e4408b 100644 --- a/examples/nextjs/app/components/ProductCard.tsx +++ b/examples/nextjs/components/ProductCard.tsx @@ -1,6 +1,6 @@ import Link from "next/link"; -import { formatMoney } from "../lib/money"; +import { formatMoney } from "@/lib/money"; export type ProductCardData = { handle: string; diff --git a/examples/nextjs/app/components/ProductDetails.tsx b/examples/nextjs/components/ProductDetails.tsx similarity index 98% rename from examples/nextjs/app/components/ProductDetails.tsx rename to examples/nextjs/components/ProductDetails.tsx index ffc89cd07f..0e58c2c2f1 100644 --- a/examples/nextjs/app/components/ProductDetails.tsx +++ b/examples/nextjs/components/ProductDetails.tsx @@ -6,9 +6,10 @@ import { createProductComponents, ShopPayButton } from "@shopify/hydrogen/react" import { useRouter, useSearchParams } from "next/navigation"; import { useState } from "react"; -import { openCartDrawer } from "../lib/cart-drawer"; -import { formatMoney } from "../lib/money"; -import type { PRODUCT_QUERY } from "../products/[handle]/page"; +import type { PRODUCT_QUERY } from "@/app/products/[handle]/page"; +import { openCartDrawer } from "@/lib/cart-drawer"; +import { formatMoney } from "@/lib/money"; + import { ProductCard } from "./ProductCard"; import { ProductViewedTracker } from "./ProductViewedTracker"; diff --git a/examples/nextjs/app/components/ProductViewedTracker.tsx b/examples/nextjs/components/ProductViewedTracker.tsx similarity index 94% rename from examples/nextjs/app/components/ProductViewedTracker.tsx rename to examples/nextjs/components/ProductViewedTracker.tsx index 466d84d5b7..e1c59e83e3 100644 --- a/examples/nextjs/app/components/ProductViewedTracker.tsx +++ b/examples/nextjs/components/ProductViewedTracker.tsx @@ -2,7 +2,7 @@ import { useEffect } from "react"; -import { getAnalytics, analyticsShop, AnalyticsEvent } from "../lib/analytics"; +import { getAnalytics, analyticsShop, AnalyticsEvent } from "@/lib/analytics"; type Props = { product: { diff --git a/examples/nextjs/app/components/Providers.tsx b/examples/nextjs/components/Providers.tsx similarity index 86% rename from examples/nextjs/app/components/Providers.tsx rename to examples/nextjs/components/Providers.tsx index e082f94d86..8a26408385 100644 --- a/examples/nextjs/app/components/Providers.tsx +++ b/examples/nextjs/components/Providers.tsx @@ -1,6 +1,6 @@ "use client"; -import { CartProvider } from "../lib/cart"; +import { CartProvider } from "@/lib/cart"; type CartProviderProps = Parameters[0]; diff --git a/examples/nextjs/app/lib/analytics.ts b/examples/nextjs/lib/analytics.ts similarity index 100% rename from examples/nextjs/app/lib/analytics.ts rename to examples/nextjs/lib/analytics.ts diff --git a/examples/nextjs/app/lib/cart-drawer.ts b/examples/nextjs/lib/cart-drawer.ts similarity index 100% rename from examples/nextjs/app/lib/cart-drawer.ts rename to examples/nextjs/lib/cart-drawer.ts diff --git a/examples/nextjs/app/lib/cart-handlers.ts b/examples/nextjs/lib/cart-handlers.ts similarity index 100% rename from examples/nextjs/app/lib/cart-handlers.ts rename to examples/nextjs/lib/cart-handlers.ts diff --git a/examples/nextjs/app/lib/cart.ts b/examples/nextjs/lib/cart.ts similarity index 100% rename from examples/nextjs/app/lib/cart.ts rename to examples/nextjs/lib/cart.ts diff --git a/examples/nextjs/app/lib/collection.ts b/examples/nextjs/lib/collection.ts similarity index 97% rename from examples/nextjs/app/lib/collection.ts rename to examples/nextjs/lib/collection.ts index c43160bc6d..270f22fab1 100644 --- a/examples/nextjs/app/lib/collection.ts +++ b/examples/nextjs/lib/collection.ts @@ -6,7 +6,7 @@ import { import { gql, type StorefrontClient } from "@shopify/hydrogen"; import type { ProductFilter as SfapiProductFilter } from "@shopify/hydrogen/storefront-api-types"; -import type { ProductCardData } from "../components/ProductCard"; +import type { ProductCardData } from "@/components/ProductCard"; const PRODUCTS_PER_PAGE = 24; diff --git a/examples/nextjs/app/lib/money.ts b/examples/nextjs/lib/money.ts similarity index 100% rename from examples/nextjs/app/lib/money.ts rename to examples/nextjs/lib/money.ts diff --git a/examples/nextjs/app/lib/search.ts b/examples/nextjs/lib/search.ts similarity index 98% rename from examples/nextjs/app/lib/search.ts rename to examples/nextjs/lib/search.ts index 88c94cfa72..f70769bba9 100644 --- a/examples/nextjs/app/lib/search.ts +++ b/examples/nextjs/lib/search.ts @@ -9,7 +9,7 @@ import type { SearchSortKeys, } from "@shopify/hydrogen/storefront-api-types"; -import type { ProductCardData } from "../components/ProductCard"; +import type { ProductCardData } from "@/components/ProductCard"; const PRODUCTS_PER_PAGE = 24; diff --git a/examples/nextjs/app/lib/storefront.ts b/examples/nextjs/lib/storefront.ts similarity index 100% rename from examples/nextjs/app/lib/storefront.ts rename to examples/nextjs/lib/storefront.ts diff --git a/examples/nextjs/app/lib/url.ts b/examples/nextjs/lib/url.ts similarity index 100% rename from examples/nextjs/app/lib/url.ts rename to examples/nextjs/lib/url.ts diff --git a/examples/nextjs/proxy.ts b/examples/nextjs/proxy.ts index b1aefa85e6..f42049dde0 100644 --- a/examples/nextjs/proxy.ts +++ b/examples/nextjs/proxy.ts @@ -5,7 +5,7 @@ import { handleShopifyRoutes } from "@shopify/hydrogen"; import { createStorefrontClient, createStorefrontRequestContext } from "@shopify/hydrogen"; import { NextResponse, type NextRequest } from "next/server"; -import { cartHandlers } from "./app/lib/cart-handlers"; +import { cartHandlers } from "@/lib/cart-handlers"; export async function proxy(request: NextRequest) { const requestContext = createStorefrontRequestContext(request); From f29426291b281cf42a8c6a5d57ede51baa7dc4ae Mon Sep 17 00:00:00 2001 From: malewis5 <29663600+malewis5@users.noreply.github.com> Date: Wed, 17 Jun 2026 17:41:19 -0400 Subject: [PATCH 2/4] examples/nextjs: server components + Suspense streaming for home & header Restructure the Next.js example toward better App Router patterns (and toward Cache Components): - Extract CartButton as a client island so Header can be a server component that fetches its own collections. - Split the home product grid into a static shell + a Suspense-streamed ProductList with a skeleton fallback. - Drop force-dynamic and the blocking cart/collections fetch from the root layout; data is colocated in the components that need it and the request-scoped storefront client stays React.cache()-memoized. - Cart is seeded client-side by CartProvider's built-in fetch instead of a blocking server seed, keeping the layout non-blocking. See the Providers TODO for the promise + React.use() path that needs core support. - Load standard-actions.js with afterInteractive so next/script injects it via an effect rather than rendering a