fix(examples): make a fresh clone run with zero setup (token fallback + dev race)#3825
Open
div-cowboy wants to merge 7 commits into
Open
fix(examples): make a fresh clone run with zero setup (token fallback + dev race)#3825div-cowboy wants to merge 7 commits into
div-cowboy wants to merge 7 commits into
Conversation
A fresh clone hits a 503 on SSR because the shared getPrivateStorefrontToken() helper only reads the committed (empty) secrets.ts, and the error it throws points at "pnpm run examples:secrets:decrypt" — a script that didn't exist. Even with it, ejson decrypt needs the EJSON key, which external contributors don't have, so the demo token was unreachable. - Fall back to process.env.PRIVATE_STOREFRONT_API_TOKEN in the shared helper, matching the env-first pattern the hydrogen example already uses, so the Next.js/React Router/SvelteKit/Astro/Solid/Nuxt examples all gain an env-var escape hatch. - Add the missing examples:secrets:decrypt script (mirrors :encrypt). - Reword the error so it's accurate for both external users (set the env var) and maintainers (decrypt + update secrets.ts). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lback A fresh clone 503s on SSR for every framework example: the shared getPrivateStorefrontToken() throws when no PRIVATE_STOREFRONT_API_TOKEN is set, and external contributors can't supply one (the demo store's private token is encrypted with a Shopify-internal EJSON key). Add a shared createExampleStorefrontClient() factory that builds a private (buyer-isolated SSR) client when a private token is available and otherwise falls back to the public token already committed in config.ts — so `pnpm dev` renders the hydrogen-preview demo store with no setup. Setting PRIVATE_STOREFRONT_API_TOKEN still opts into the private path. - examples/shared/storefront-client.ts: the factory. Public and private clients share the same graphql/requestContext surface and nothing branches on the client's `type`, so the fallback is transparent to the cart-routes/redirects helpers (typed for the private client). - examples/shared/private-env.ts: add non-throwing getOptionalPrivateStorefrontToken(). - Route nextjs, react-router, sveltekit, astro, solid-start, and nuxt through the factory. - examples/shared becomes a workspace package so it can resolve @shopify/hydrogen (it's the first shared module to import it). The hydrogen (Oxygen) example uses a different store config and is left as a follow-up. Verified: `pnpm dev:next` serves / and /collections with HTTP 200 (was 503); typecheck (9/9) and lint pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The shared storefront-client module is pulled into each example via the @shared/* path alias, so a *value* import of @shopify/hydrogen forced every example's bundler (Turbopack, Vite, …) to resolve the package from examples/shared. That resolution relies on a pnpm symlink that the bundlers handle unreliably, producing: Module not found: Can't resolve '@shopify/hydrogen' Import @shopify/hydrogen as a type only (erased at runtime, so no bundler resolves it from examples/shared) and inject `createStorefrontClient` from each example, where the package has always resolved. The single public->private cast stays in the shared factory. Verified with examples/shared/node_modules removed entirely (the broken state): `pnpm dev:next` and `dev:rr` serve / and /collections at HTTP 200 with no module-not-found. typecheck (9/9) and lint still pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
div-cowboy
force-pushed
the
fix/examples-private-token-fallback
branch
from
June 30, 2026 22:06
4770627 to
c27c7fa
Compare
oxfmt's sortImports groups third-party imports before relative ones. Reorder so `pnpm run format:check` passes in CI. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Author
|
I have signed the CLA! |
`pnpm dev` runs `@shopify/hydrogen#build` (one-shot, via turbo `^build`) and then `@shopify/hydrogen#dev` (`tsdown --watch`). The watch's default startup clean wipes the just-built `dist/` while the example bundlers are reading it, spewing transient "Module not found: Can't resolve '@shopify/hydrogen'" at every example. Disable clean in `--watch` mode only: `dist/` stays populated from the build, rolldown writes each output atomically, so example bundlers always resolve a complete module. Production builds still clean; SDK hot reload is preserved (verified: edit src -> tsdown rebuilds -> example reloads). This replaces the dev-script workaround from Shopify#3826 (which removed the watch and so lost SDK hot reload). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`pnpm dev` runs all example apps at once, but `next dev` and `nuxt dev` both default to :3000, so they collided non-deterministically — opening localhost:3000 returned whichever won the race (or nothing). Pin a distinct port per example (nextjs 3000, nuxt 3001, react-router 3003, sveltekit 3004, astro 3005, solid-start 3006; the Oxygen app keeps 3002), and add the missing per-example dev scripts (`dev:nuxt`, `dev:svelte`, `dev:astro`, `dev:solid`) so any single storefront can be run on a known port. `pnpm dev` now starts all of them with no collision. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TLDR
When an engineer pulls down the hydrogen examples, they should be able to just run
pnpm installandpnpm devand have all of the apps working on different ports. This still throws the message letting them know that if they want to overwrite the default Shopify token, they can do so in the console.Problem
A fresh clone of the
previewbranch can't cleanly run the example apps. Three things break a zero-setuppnpm dev:SSR 500s — every example throws on the first request:
The referenced
examples:secrets:decryptscript didn't exist, and even with itejson decryptneeds a Shopify-internal key — so external contributors have no path to the demo store's private token, and the shared helper just threw.A wall of "Module not found" at startup —
pnpm devspews transient errors before settling:Port collisions —
pnpm devruns all example apps at once, butnext devandnuxt devboth default to:3000, so they fought over the port non-deterministically. Openinglocalhost:3000returned whichever won the race (or nothing).Together these mean a fresh clone can't just
pnpm devand see a storefront.Fix
1. Zero-config public-token fallback (the 500)
The demo store's public Storefront token is already committed in
examples/shared/config.ts, andcreateStorefrontClientsupportstype: "public". New shared factoryexamples/shared/storefront-client.ts:createExampleStorefrontClient()builds atype: "private"(buyer-isolated SSR) client whenPRIVATE_STOREFRONT_API_TOKENis set (env first, thensecrets.ts), and otherwise falls back to atype: "public"client using the committed demo-store token — so a fresh clone renders thehydrogen-previewdemo store with zero setup. Warns once when falling back.@shopify/hydrogenis imported type-only here andcreateStorefrontClientis injected by each caller, so the shared module (pulled into every example via the@shared/*path alias) carries no runtime dependency on the package.2. Stop the
@shopify/hydrogenwatch from racing example bundlers (the "Module not found" wall)Root cause:
pnpm devruns@shopify/hydrogen#build(one-shot, via turbo^build) and@shopify/hydrogen#dev(tsdown --watch). The watch's default startup clean wipes the just-builtdist/while the example bundlers are reading it.Fix (
packages/hydrogen/tsdown.config.ts): skip the clean in--watchmode only.dist/stays populated from the build, rolldown writes each output atomically, so example bundlers always resolve a complete module. Production builds still clean, and SDK hot reload is preserved (the watch keeps running).3. Deterministic ports + per-example dev scripts (the collision)
Pin a distinct port per example so
pnpm devruns all of them at once with no collision, and each can be opened at a known URL:Added the missing per-example scripts so any single storefront runs on its own port with SDK hot reload:
dev:nuxt,dev:svelte,dev:astro,dev:solid(joining the existingdev:next,dev:rr,dev:hydrogen).Verification
Fresh-clone simulation, all on this branch:
pnpm dev(all 7 servers): each binds its assigned port, 0 collisions, 0 "Module not found" errors (previously ~22).Mock.shop — Hydrogen(demo store) with no token configured.pnpm dev:svelte) run a single app on its port; SDK hot reload confirmed (editpackages/hydrogen/src→tsdownrebuilds → example reloads).@shopify/hydrogenbuild still cleansdist/.typecheck,oxlint(0/0), andoxfmtall clean on changed files.PRIVATE_STOREFRONT_API_TOKENstill routes through the private (buyer-isolated) client.