Template
Move Storefront queries onto @shopify/hydrogen preview client
Replaces the hand-rolled shopifyFetch wrapper and raw query strings with
the hydrogen preview package (0.0.0-preview-116d5d7-20260730141607), the
same build pinned in hydrogen-preview/.
- services/shopify/client: createStorefrontClient({type: "public"}) with a
static request context. Two module-scoped clients — uncached for carts,
products and customers; revalidate-3600 for shop policies — using the
client's custom fetch option to carry Next's caching hints.
- graphql/*: every document wrapped in gql(), fragments composed via the
second argument instead of string interpolation, and $country/$language
declared with @inContext so hydrogen injects them.
- shopifyFetch keeps its {query, variables} call shape so call sites are
unchanged, but is now generic over the document, so data is inferred. It
re-raises GraphQL errors, which hydrogen returns rather than throws.
Env var names, the 2025-07 API version pin, and the client-side fetching
architecture are unchanged.
Turning on type coverage surfaced real defects, not just annotations:
- hydrogen gql check caught $discountCodes: [String!] used where the field
requires [String!]!.
- Cart and customer mutation payloads are nullable and were dereferenced
unconditionally, so a failed mutation threw a TypeError. Adds a shared
unwrapCartPayload helper (collapsing five copies of the same userErrors
check) and explicit null handling in the customer service, so a null
payload reads as an error rather than success with no errors.
- Search results are a Product | Page | Article union; adds __typename to
the queries and narrows on it.
- Widens nullable fields (altText, image, customer.email, totalTaxAmount)
in the domain interfaces and in the structural duplicates some
components declare locally.
Adds a typecheck script (tsc --noEmit && hydrogen gql check); it passes
with 0 errors. Two deprecation warnings are left alone as acting on them
would change behaviour: ProductOption.values and CartCost.totalTaxAmount.
Verified against mock.shop: build prerenders the policy pages through the
cached client, and in the browser the product grid, search with facets,
collection filter round-trip, add-to-cart and quantity update all work.
Customer account flows are untested — they need real credentials.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JaxgqPbFxLsSuLPZom2kdC
This commit is contained in:
co-authored by
Claude Opus 5
parent
95b191eeb8
commit
f8c3ef9e0e
@@ -18,7 +18,8 @@ import {
|
||||
export const CUSTOMER_TOKEN_COOKIE = 'customerAccessToken';
|
||||
|
||||
export interface CustomerUserError {
|
||||
code?: string;
|
||||
/** One of `CustomerErrorCode`; kept as a string so new codes don't break. */
|
||||
code?: string | null;
|
||||
field?: string[] | null;
|
||||
message: string;
|
||||
}
|
||||
@@ -57,7 +58,7 @@ export interface CustomerOrder {
|
||||
|
||||
export interface Customer {
|
||||
id: string;
|
||||
email: string;
|
||||
email: string | null;
|
||||
firstName?: string | null;
|
||||
lastName?: string | null;
|
||||
phone?: string | null;
|
||||
@@ -84,6 +85,13 @@ const firstMessage = (errors: CustomerUserError[]) =>
|
||||
|
||||
export { firstMessage as customerErrorMessage };
|
||||
|
||||
// Shopify returns a null mutation payload when the mutation could not run at
|
||||
// all. That is not a success, so it surfaces as a generic error rather than an
|
||||
// empty error list, which callers read as "it worked".
|
||||
const MUTATION_FAILED: CustomerUserError[] = [
|
||||
{ message: 'Something went wrong. Please try again.' },
|
||||
];
|
||||
|
||||
export async function createCustomer(input: {
|
||||
email: string;
|
||||
password: string;
|
||||
@@ -96,7 +104,10 @@ export async function createCustomer(input: {
|
||||
variables: { input },
|
||||
});
|
||||
|
||||
return { errors: response.data.customerCreate.customerUserErrors ?? [] };
|
||||
const result = response.data.customerCreate;
|
||||
if (!result) return { errors: MUTATION_FAILED };
|
||||
|
||||
return { errors: result.customerUserErrors ?? [] };
|
||||
}
|
||||
|
||||
export async function login(
|
||||
@@ -109,6 +120,7 @@ export async function login(
|
||||
});
|
||||
|
||||
const result = response.data.customerAccessTokenCreate;
|
||||
if (!result) return { token: null, errors: MUTATION_FAILED };
|
||||
|
||||
return {
|
||||
token: result.customerAccessToken ?? null,
|
||||
@@ -152,6 +164,7 @@ export async function resetPassword(
|
||||
});
|
||||
|
||||
const result = response.data.customerReset;
|
||||
if (!result) return { token: null, errors: MUTATION_FAILED };
|
||||
|
||||
return {
|
||||
token: result.customerAccessToken ?? null,
|
||||
@@ -170,6 +183,7 @@ export async function activateAccount(
|
||||
});
|
||||
|
||||
const result = response.data.customerActivate;
|
||||
if (!result) return { token: null, errors: MUTATION_FAILED };
|
||||
|
||||
return {
|
||||
token: result.customerAccessToken ?? null,
|
||||
@@ -210,6 +224,7 @@ export async function updateCustomer(
|
||||
});
|
||||
|
||||
const result = response.data.customerUpdate;
|
||||
if (!result) return { customer: null, errors: MUTATION_FAILED };
|
||||
|
||||
return {
|
||||
customer: result.customer ?? null,
|
||||
|
||||
Reference in New Issue
Block a user