Skip to content

examples/nextjs: server components + Suspense streaming for home & header#3810

Merged
fredericoo merged 4 commits into
Shopify:previewfrom
vercel-labs:mttlws/home-page-example-fix
Jun 24, 2026
Merged

examples/nextjs: server components + Suspense streaming for home & header#3810
fredericoo merged 4 commits into
Shopify:previewfrom
vercel-labs:mttlws/home-page-example-fix

Conversation

@malewis5

@malewis5 malewis5 commented Jun 17, 2026

Copy link
Copy Markdown

Stacked on #3811 — this PR is based on the `lib/`+`components/` hoist in #3811. GitHub shows a cumulative diff until #3811 merges; review the home/header commits only, and merge #3811 first.

WHY are these changes introduced?

The Next.js example used patterns that block streaming and aren't idiomatic App Router: a "use client" header with prop-drilled data, a force-dynamic layout that blocks on cart + collections before anything paints, and a home page that awaits all products before rendering the hero.

This restructures the example toward idiomatic App Router data-fetching and streaming — and toward cacheComponentswithout touching the @shopify/hydrogen core.

WHAT is this pull request doing?

  • Client islands: extract CartButton so Header is a server component that fetches its own collections — no "use client" on the header, no prop-drilling from the layout.
  • Streaming: split the home grid into a static ProductListShell + a <Suspense>-streamed ProductList with a skeleton fallback, so the hero paints before products resolve.
  • Non-blocking layout: drop force-dynamic and the blocking cart/collections fetch from the root layout. Data is colocated in the components that need it; getStorefrontClient stays React.cache()-memoized so shared calls dedupe within a request.
  • Cart: CartProvider seeds client-side via its built-in fetch instead of a blocking server seed, keeping the layout streamable. A Providers TODO documents the promise + React.use() path that would restore an SSR'd cart count (needs a core change, deferred).

HOW to test your changes?

  1. pnpm run dev:next
  2. Home, collections, and product pages render; the hero paints immediately while the product grid streams in behind its skeleton.
  3. Cart count hydrates client-side; add/update via the cart drawer works.

pnpm run check (format, lint, typecheck, test) passes.

Follow-ups (toward Cache Components)

  • Wrap Header's collections in <Suspense> so the nav shell doesn't block the layout (and won't taint the route under cacheComponents).
  • Enable cacheComponents + use cache on catalog fetches — requires splitting headers() out of getStorefrontClient so catalog queries (no per-request input) become cacheable, separate from cart / buyer-IP (dynamic).

Checklist

  • I've read the Contributing Guidelines
  • I've considered possible cross-platform impacts (Mac, Linux, Windows)
  • I've added a changeset — N/A, example-only change to a private package
  • I've added tests — N/A, example app
  • I've added or updated the documentation

@malewis5 malewis5 requested a review from a team as a code owner June 17, 2026 22:24
@malewis5

Copy link
Copy Markdown
Author

I have signed the CLA!

malewis5 and others added 3 commits June 17, 2026 18:47
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) <noreply@anthropic.com>
…ader

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 <script> React 19 won't
  execute on the client.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@malewis5 malewis5 force-pushed the mttlws/home-page-example-fix branch from 4a67169 to 7cbb1cf Compare June 17, 2026 22:54

@benjaminsehl benjaminsehl left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, Matt!

Add blank line between import groups to satisfy oxfmt --check.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@fredericoo fredericoo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you for the contribution! it all works as expected. looks like our generated code was misusing next a little bit

@fredericoo fredericoo merged commit 5340981 into Shopify:preview Jun 24, 2026
2 checks passed
@fredericoo

Copy link
Copy Markdown
Contributor

lol i read it backwards, thought it was to merge this one first. will fix it on my end!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants