Hide the synthetic Default Title option on single-SKU products

Shopify gives every single-variant product one option named Title with the
lone value Default Title. It isn't a real choice, so filter it out of the
product detail options, the cart line summaries, and the payload the store
assistant sees. Variant matching still reads the raw selectedOptions, so
single-SKU products keep resolving their variant.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EuRVxuyMUTzWKXW8P5Tn8A
This commit is contained in:
Rami Bitar
2026-08-04 13:20:47 -04:00
co-authored by Claude Opus 5
parent c88613de03
commit b139b30273
4 changed files with 40 additions and 9 deletions
+8 -2
View File
@@ -7,6 +7,8 @@ import {
getProductsPage,
getCollections,
getCollectionProductsPage,
isDefaultTitleOption,
isDefaultTitleSelection,
} from '@/services/shopify/catalog';
// Streaming needs the Node runtime here because the Storefront helpers run
@@ -63,7 +65,9 @@ const summariseProduct = (product: {
description: product.description?.slice(0, 300) ?? null,
productType: product.productType || null,
tags: product.tags ?? [],
options: product.options?.map((option) => ({
options: product.options
?.filter((option) => !isDefaultTitleOption(option))
.map((option) => ({
name: option.name,
values: option.values,
})),
@@ -132,7 +136,9 @@ export async function POST(req: Request) {
title: node.title,
available: node.availableForSale,
price: `${node.price.amount} ${node.price.currencyCode}`,
options: node.selectedOptions,
options: node.selectedOptions?.filter(
(option) => !isDefaultTitleSelection(option)
),
})),
};
},
+5 -1
View File
@@ -25,6 +25,7 @@ import {
EmptyDescription,
EmptyContent,
} from '@/components/ui/empty';
import { isDefaultTitleSelection } from '@/services/shopify/catalog';
const CartDrawer: React.FC = () => {
const isOpen = useCartStore((s) => s.isOpen);
@@ -102,7 +103,10 @@ const CartDrawer: React.FC = () => {
};
const getSelectedOptions = (item: (typeof items)[0]) => {
return item.merchandise.selectedOptions ?? [];
// Single-SKU items carry a synthetic `Title: Default Title` — not worth a line.
return (item.merchandise.selectedOptions ?? []).filter(
(option) => !isDefaultTitleSelection(option)
);
};
return (
@@ -5,6 +5,7 @@ import { RiSubtractLine, RiAddLine } from '@remixicon/react';
import ShopPayButton from '@/components/shopify/shop-pay-button';
import type { ProductOption, ProductOptionValue } from '@/hooks/use-shopify-products';
import { isSwatchOptionName, swatchColorForName } from '@/config/swatches';
import { isDefaultTitleOption } from '@/services/shopify/catalog';
interface ProductPrice {
amount: string;
@@ -103,9 +104,10 @@ const ProductDetailInfo: React.FC<ProductDetailInfoProps> = ({
isSwatchOptionName(option.name);
// Some products return Size before Color; show the swatches first either way.
const orderedOptions = [...(product.options ?? [])].sort(
(a, b) => Number(isSwatchOption(b)) - Number(isSwatchOption(a))
);
// Single-SKU products expose a synthetic `Title: Default Title` option — drop it.
const orderedOptions = [...(product.options ?? [])]
.filter((option) => !isDefaultTitleOption(option))
.sort((a, b) => Number(isSwatchOption(b)) - Number(isSwatchOption(a)));
// `optionValues` carries the swatch data; fall back to plain `values`.
const optionValuesFor = (option: ProductOption): ProductOptionValue[] =>
+19
View File
@@ -64,6 +64,25 @@ export interface ProductOption {
optionValues?: ProductOptionValue[];
}
/**
* Single-variant products still carry one synthetic option — `Title` with the
* lone value `Default Title`. It isn't a real choice, so keep it out of the UI.
*/
export const isDefaultTitleOption = (option: {
name: string;
values: string[];
}): boolean =>
option.name === 'Title' &&
option.values.length === 1 &&
option.values[0] === 'Default Title';
/** Same synthetic option, as it appears on a variant's `selectedOptions`. */
export const isDefaultTitleSelection = (selection: {
name: string;
value: string;
}): boolean =>
selection.name === 'Title' && selection.value === 'Default Title';
export interface Product {
id: string;
title: string;