Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ SITE_NAME="Next.js Commerce"
SHOPIFY_REVALIDATION_SECRET=""
SHOPIFY_STOREFRONT_ACCESS_TOKEN=""
SHOPIFY_STORE_DOMAIN="[your-shopify-store-subdomain].myshopify.com"
SHOPIFY_STOREFRONT_API_VERSION= "2025-07"
2 changes: 1 addition & 1 deletion lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ export const TAGS = {

export const HIDDEN_PRODUCT_TAG = 'nextjs-frontend-hidden';
export const DEFAULT_OPTION = 'Default Title';
export const SHOPIFY_GRAPHQL_API_ENDPOINT = '/api/2023-01/graphql.json';
export const SHOPIFY_GRAPHQL_API_ENDPOINT = `/api/${process.env.SHOPIFY_STOREFRONT_API_VERSION}/graphql.json`;
74 changes: 45 additions & 29 deletions lib/shopify/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,11 @@ import {
const domain = process.env.SHOPIFY_STORE_DOMAIN
? ensureStartsWith(process.env.SHOPIFY_STORE_DOMAIN, 'https://')
: '';
const endpoint = `${domain}${SHOPIFY_GRAPHQL_API_ENDPOINT}`;
const key = process.env.SHOPIFY_STOREFRONT_ACCESS_TOKEN!;

export const endpoint = `${domain}${SHOPIFY_GRAPHQL_API_ENDPOINT}`;

// Your Shopify Storefront access token
export const key = process.env.SHOPIFY_STOREFRONT_ACCESS_TOKEN!;

type ExtractVariables<T> = T extends { variables: object }
? T['variables']
Expand All @@ -76,7 +79,7 @@ export async function shopifyFetch<T>({
headers?: HeadersInit;
query: string;
variables?: ExtractVariables<T>;
}): Promise<{ status: number; body: T } | never> {
}): Promise<{ status: number; body: T }> {
try {
const result = await fetch(endpoint, {
method: 'POST',
Expand All @@ -93,28 +96,35 @@ export async function shopifyFetch<T>({

const body = await result.json();

// Handle API-level errors
if (body.errors) {
throw body.errors[0];
console.error('[shopifyFetch] Shopify API error:', {
query,
status: result.status,
error: body.errors[0]
});

// Throw a standard Error object (not a plain object)
throw new Error(
`Shopify API Error (${result.status}): ${body.errors[0].message}`
);
}

return {
status: result.status,
body
};
} catch (e) {
if (isShopifyError(e)) {
throw {
cause: e.cause?.toString() || 'unknown',
status: e.status || 500,
message: e.message,
query
};
}
} catch (err: any) {
// Log full context for unknown or fetch-level errors
console.error('[shopifyFetch] Unexpected fetch error:', {
query,
error: err
});

throw {
error: e,
query
};
// Re-throw a standard Error to be caught elsewhere
throw new Error(
`Unexpected error in shopifyFetch: ${err?.message ?? err}`
);
}
}

Expand Down Expand Up @@ -264,23 +274,29 @@ export async function updateCart(
}

export async function getCart(): Promise<Cart | undefined> {
const cartId = (await cookies()).get('cartId')?.value;
try {
const cartId = (await cookies()).get('cartId')?.value;

if (!cartId) {
return undefined;
}
if (!cartId) {
console.warn('[getCart] No cartId in cookies');
return undefined;
}

const res = await shopifyFetch<ShopifyCartOperation>({
query: getCartQuery,
variables: { cartId }
});
const res = await shopifyFetch<ShopifyCartOperation>({
query: getCartQuery,
variables: { cartId }
});

// Old carts becomes `null` when you checkout.
if (!res.body.data.cart) {
if (!res.body?.data?.cart) {
console.warn('[getCart] Cart is null for cartId:', cartId);
return undefined;
}

return reshapeCart(res.body.data.cart);
} catch (err) {
console.error('[getCart] Failed:', err);
return undefined;
}

return reshapeCart(res.body.data.cart);
}

export async function getCollection(
Expand Down