diff --git a/app/account/page.tsx b/app/account/page.tsx index d057c3d..4dd9be4 100644 --- a/app/account/page.tsx +++ b/app/account/page.tsx @@ -37,8 +37,7 @@ type AccountPageProps = { /** * `/account` — core Customer Accounts surface (name + email only). * - * No `export const dynamic` / `export const fetchCache`: under - * `cacheComponents: true`, `force-dynamic` is not allowed. The page relies on + * No `export const dynamic` / `export const fetchCache`: the page relies on * the `AppShell` dynamic context (`await connection()` in the root layout's * shell), which opts the whole subtree into dynamic rendering — any * `headers()`/`cookies()` read inside (e.g. `getCustomerAccessToken()` → diff --git a/app/app-shell.tsx b/app/app-shell.tsx index 0d1bccd..925d0cb 100644 --- a/app/app-shell.tsx +++ b/app/app-shell.tsx @@ -17,9 +17,9 @@ import { Providers } from "./providers"; /** * Async server shell that owns the per-request (dynamic) reads: the cart seed - * + the shop analytics GID. With `cacheComponents: true`, uncached/dynamic data - * accessed in a Server Component must sit inside a `` boundary so the - * static HTML shell prerenders and the per-buyer parts stream + * + the shop analytics GID. The dynamic data access in this Server Component + * sits inside a `` boundary so the static HTML shell prerenders and + * the per-buyer parts stream * (`next/server` `connection()` + `headers()`/`cookies()` are per-request). * * Rendered inside `` from the root layout. `await connection()` diff --git a/app/cart/page.tsx b/app/cart/page.tsx index 14fe08d..530657a 100644 --- a/app/cart/page.tsx +++ b/app/cart/page.tsx @@ -26,9 +26,9 @@ export const metadata: Metadata = { * `CartProvider` would create a divergent inner store vs the root-rendered * `CartDrawer` — mutations would not reflect across them. * - * `force-dynamic` is not allowed with `cacheComponents: true`; the root layout - * already makes every route dynamic via `cookies()`/`headers()` in the cart - * seed. Reachable via the footer `/cart` link (no-JS fallback). + * No `force-dynamic` needed; the root layout already makes every route + * dynamic via `cookies()`/`headers()` in the cart seed. Reachable via the + * footer `/cart` link (no-JS fallback). */ export default function CartPage() { return ( diff --git a/app/collections/[handle]/page.tsx b/app/collections/[handle]/page.tsx index 894371d..263c44b 100644 --- a/app/collections/[handle]/page.tsx +++ b/app/collections/[handle]/page.tsx @@ -1,7 +1,6 @@ import { parseCollectionParams, type AvailableFilter } from "@shopify/hydrogen"; import type { ProductFilter as StorefrontApiProductFilter } from "@shopify/hydrogen/storefront-api-types"; import type { Metadata } from "next"; -import { cacheLife, cacheTag } from "next/cache"; import { notFound } from "next/navigation"; import { CollectionBrowser } from "@/components/CollectionBrowser"; @@ -43,13 +42,7 @@ async function fetchCollection( availableFilters: AvailableFilter[]; pageInfo: { hasNextPage: boolean; endCursor?: string | null }; }> { - "use cache"; - cacheLife("minutes"); - cacheTag("collections", "products"); - - // Reconstruct URLSearchParams from the serialized search string. `use cache` - // serializes arguments, so a URLSearchParams passed in loses `.get` — pass a - // plain string across the cache boundary and parse inside. + // Reconstruct URLSearchParams from the serialized search string. const browse = parseCollectionParams(new URLSearchParams(searchString)); const result = await query(handle, after, browse.filters, browse.sortKey, browse.reverse); return result; diff --git a/app/collections/page.tsx b/app/collections/page.tsx index 706da98..c11ea8e 100644 --- a/app/collections/page.tsx +++ b/app/collections/page.tsx @@ -1,5 +1,4 @@ import type { Metadata } from "next"; -import { cacheLife, cacheTag } from "next/cache"; import Link from "next/link"; import { Breadcrumbs } from "@/components/Breadcrumbs"; @@ -21,10 +20,6 @@ export const metadata: Metadata = { }; async function fetchCollections(after?: string) { - "use cache"; - cacheLife("hours"); - cacheTag("collections"); - const { data, errors } = await staticStorefrontClient.graphql(COLLECTIONS_QUERY, { variables: { first: 24, after }, }); diff --git a/app/layout.tsx b/app/layout.tsx index 8730291..de5df25 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -8,8 +8,8 @@ import { SITE_ORIGIN } from "@/lib/site"; import { AppShell } from "./app-shell"; /** - * Root layout (engineering.md F1, F4, F7, F10). With `cacheComponents: true`, - * the layout is a **static shell** — it prerenders the ``/`` + + * Root layout (engineering.md F1, F4, F7, F10). The layout is a + * **static shell** — it prerenders the ``/`` + * announcement bar, then wraps the per-request (dynamic) `AppShell` (cart seed * + analytics shop + chrome) in `` so the dynamic parts stream while * the static shell serves immediately. `AppShell` calls `connection()` to opt diff --git a/app/not-found.tsx b/app/not-found.tsx index 4dbbe05..bb40f56 100644 --- a/app/not-found.tsx +++ b/app/not-found.tsx @@ -13,8 +13,8 @@ import { getStorefrontClient } from "@/lib/storefront"; * `references/nextjs.md`). `proxy.ts` cannot inspect the routed response, so * Storefront URL redirects run here, after the framework returns a 404. * - * With `cacheComponents: true`, the per-request reads (`headers()` + - * `getStorefrontClient()` + `redirect()`) must sit inside a `` + * The per-request reads (`headers()` + `getStorefrontClient()` + + * `redirect()`) sit inside a `` * boundary. The static 404 shell prerenders; the `` streams * and either `redirect()`s to a matching Shopify URL redirect or renders * nothing (leaving the 404 shell visible). diff --git a/app/page.tsx b/app/page.tsx index d0ce171..db7f460 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,5 +1,4 @@ import type { Metadata } from "next"; -import { cacheLife, cacheTag } from "next/cache"; import Link from "next/link"; import { CollectionCard } from "@/components/CollectionCard"; @@ -22,13 +21,8 @@ export const metadata: Metadata = { twitter: { card: "summary_large_image" }, }; -/** Home catalog fetch — cached at the data boundary (F2: dynamic root layout - * precludes page-level prerender, so the cache-point is the fetch function). */ +/** Home catalog fetch. */ async function fetchHome() { - "use cache"; - cacheLife("minutes"); - cacheTag("products", "collections"); - const { data, errors } = await staticStorefrontClient.graphql(HOME_QUERY); if (errors) { console.error("[hydrogen] Home query failed", errors); diff --git a/app/products/[handle]/page.tsx b/app/products/[handle]/page.tsx index 9024288..10ab4ce 100644 --- a/app/products/[handle]/page.tsx +++ b/app/products/[handle]/page.tsx @@ -1,6 +1,5 @@ import { getSelectedProductOptions } from "@shopify/hydrogen"; import type { Metadata } from "next"; -import { cacheLife, cacheTag } from "next/cache"; import { notFound } from "next/navigation"; import { Suspense } from "react"; @@ -19,8 +18,6 @@ type Props = { export async function generateMetadata({ params }: Props): Promise { const { handle } = await params; - // Re-fetch the product for metadata. It hits the same `use cache` cache-point - // as the page render, so this is a cache hit in practice. const { product } = await fetchProduct(handle, ""); const title = product?.title ?? "Product"; return { @@ -41,15 +38,9 @@ async function fetchProduct( handle: string, searchString: string, ): Promise<{ product: ProductData | null }> { - "use cache"; - cacheLife("minutes"); - cacheTag("products"); - // Read variant selection from URL option params (F4: a no-JS GET to // `?Size=Large&Color=Green` resolves the variant server-side). Do NOT pass // `optionNames: []` — an empty allow-list filters out every param. - // Reconstruct URLSearchParams from the serialized search string — `use cache` - // serializes arguments, so a URLSearchParams passed in loses `.get`. const selectedOptions = getSelectedProductOptions(new URLSearchParams(searchString)); const { data, errors } = await staticStorefrontClient.graphql(PRODUCT_QUERY, { @@ -89,13 +80,9 @@ export default async function ProductPage({ params, searchParams }: Props) { /** * Related products (`notes/product.md` "you may also like"). Best-effort: any * error/timeout returns an empty list so the PDP never breaks. Fetched via the - * shared `staticStorefrontClient` inside a `use cache` cache-point. + * shared `staticStorefrontClient`. */ async function RelatedProducts({ handle }: { handle: string }) { - "use cache"; - cacheLife("minutes"); - cacheTag("products"); - let related: ProductCardData[] = []; try { const { data, errors } = await staticStorefrontClient.graphql(RELATED_PRODUCTS_QUERY, { diff --git a/app/search/page.tsx b/app/search/page.tsx index 53c8327..5f8afd1 100644 --- a/app/search/page.tsx +++ b/app/search/page.tsx @@ -2,7 +2,6 @@ import { parseCollectionParams, type StorefrontApi } from "@shopify/hydrogen"; import type { AvailableFilter } from "@shopify/hydrogen"; import type { ProductFilter as StorefrontApiProductFilter } from "@shopify/hydrogen/storefront-api-types"; import type { Metadata } from "next"; -import { cacheLife, cacheTag } from "next/cache"; import { CollectionBrowser } from "@/components/CollectionBrowser"; import { SEARCH_QUERY } from "@/lib/queries"; @@ -37,10 +36,6 @@ type SearchNode = NonNullable< type SearchProductNode = Extract; async function fetchSearch(term: string, searchString: string): Promise { - "use cache"; - cacheLife("minutes"); - cacheTag("products"); - if (!term) { return { term: "", @@ -51,9 +46,7 @@ async function fetchSearch(term: string, searchString: string): Promise { const entries = await fetchSitemap(); @@ -23,10 +21,6 @@ export default async function sitemap(): Promise { type SitemapEntry = { loc: string; lastmod?: string }; async function fetchSitemap(): Promise { - "use cache"; - cacheLife("hours"); - cacheTag("products", "collections"); - const { data, errors } = await staticStorefrontClient.graphql(SITEMAP_QUERY); if (errors) { console.error("[hydrogen] Sitemap query failed", errors); diff --git a/components/CollectionBrowser.tsx b/components/CollectionBrowser.tsx index 86c8872..a287366 100644 --- a/components/CollectionBrowser.tsx +++ b/components/CollectionBrowser.tsx @@ -21,7 +21,7 @@ import { FilterGroup } from "@/lib/filters"; /** * Shared collection/search browser (`hydrogen-collection-browser` / * `references/nextjs.md`). The server page fetches the product + filter - * snapshot via `staticStorefrontClient` inside a `use cache` cache-point and + * snapshot via `staticStorefrontClient` and * passes it here. This client component owns the `CollectionProvider`, * `useCollection`/`useCollectionForm`, URL sync via `useRouter`/`useSearchParams`, * and the filter/sort/grid UI. diff --git a/lib/analytics-shop.ts b/lib/analytics-shop.ts index 1b1cf03..7e62688 100644 --- a/lib/analytics-shop.ts +++ b/lib/analytics-shop.ts @@ -1,16 +1,14 @@ import "server-only"; import { analyticsShop as analyticsShopConfig, shop as shopConfig } from "@shared/config"; -import { cacheLife, cacheTag } from "next/cache"; import { SHOP_ANALYTICS_QUERY } from "@/lib/queries"; import { staticStorefrontClient } from "@/lib/storefront-static"; /** * Resolve the shop analytics GID best-effort + non-blocking (engineering.md F1). - * The query runs inside a `'use cache'` cache-point (`cacheLife("hours")`, - * `cacheTag("shop")`) so warm requests resolve instantly. On timeout/error we - * fall back to the config-derived shop GID/name (avoids drift — `@shared/config` - * `analyticsShop.shopId` is already `gid://shopify/Shop/${shop.shopId}`). + * On timeout/error we fall back to the config-derived shop GID/name (avoids + * drift — `@shared/config` `analyticsShop.shopId` is already + * `gid://shopify/Shop/${shop.shopId}`). */ export type AnalyticsShop = { shopId: string; @@ -24,12 +22,7 @@ const SHOP_FALLBACK: AnalyticsShop = { shopDescription: null, }; -/** Cache the shop query result for hours (it almost never changes). */ async function fetchShopAnalytics(): Promise { - "use cache"; - cacheLife("hours"); - cacheTag("shop"); - const { data, errors } = await staticStorefrontClient.graphql(SHOP_ANALYTICS_QUERY); if (errors) { console.error("[hydrogen] Root shop query failed", errors); @@ -42,7 +35,7 @@ async function fetchShopAnalytics(): Promise { } /** - * Best-effort, non-blocking shop analytics resolution. Races the cached query + * Best-effort, non-blocking shop analytics resolution. Races the query * against a 2000ms timeout; on timeout/error falls back to `@shared/config`. * Merges the resolved GID/name with the config-derived `analyticsShop` * metadata (acceptedLanguage/currency/hydrogenSubchannelId). diff --git a/lib/storefront-static.ts b/lib/storefront-static.ts index f73b340..7c0e94f 100644 --- a/lib/storefront-static.ts +++ b/lib/storefront-static.ts @@ -14,9 +14,8 @@ import { resolveStorefrontConfig } from "./storefront-config"; * sitemap, related products, shop analytics GID) fetch through this client. * Only the cart seed uses the per-buyer `getStorefrontClient()`. * - * Caching lives at the `use cache` boundary (cache-points keyed by serializable - * inputs). The `cache:` option is never passed to `graphql()` — Next native - * data cache + `cacheLife`/`cacheTag` replace the Oxygen sub-request LRU. + * The `cache:` option is never passed to `graphql()` (no Oxygen sub-request + * LRU in this Next.js port). */ const requestContext = createShopifyRequestContext({ request: { headers: new Headers() }, diff --git a/next.config.ts b/next.config.ts index ef0608f..103221b 100644 --- a/next.config.ts +++ b/next.config.ts @@ -3,14 +3,10 @@ import type { NextConfig } from "next"; /** * Next.js 16 config for the Hydrogen example. * - * `cacheComponents: true` enables Cache Components / `use cache` (engineering.md - * F2 native Next caching instead of the Oxygen sub-request LRU). - * * No `next/image` remote patterns — we use plain `` + the `hydrogen-image` * helper (F12; Hydrogen ships no Image component and we size CDN URLs ourselves). */ const nextConfig: NextConfig = { - cacheComponents: true, // React Strict Mode is disabled because `@shopify/hydrogen/react`'s // `PredictiveSearchProvider` destroys its store in the Strict Mode effect // cleanup (double-invoke), and `useMemo` returns the same (now-destroyed)