Skip to content

Version Packages (canary)#2853

Open
github-actions[bot] wants to merge 1 commit intocanaryfrom
changeset-release/canary
Open

Version Packages (canary)#2853
github-actions[bot] wants to merge 1 commit intocanaryfrom
changeset-release/canary

Conversation

@github-actions
Copy link
Contributor

@github-actions github-actions bot commented Jan 29, 2026

This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to canary, this PR will be updated.

Releases

@bigcommerce/catalyst-core@1.5.0

Minor Changes

  • #2801 18cfdc8 Thanks @Tharaae! - Fetch product inventory data with a separate GQL query with no caching

    Migration

    The files to be rebased for this change to be applied are:

    • core/app/[locale]/(default)/product/[slug]/page-data.ts
    • core/app/[locale]/(default)/product/[slug]/page.tsx
  • #2863 6a23c90 Thanks @jorgemoya! - Add pagination support for the product gallery. When a product has more images than the initial page load, new images will load as batches once the user reaches the end of the existing thumbnails. Thumbnail images now will display in horizontal direction in all viewport sizes.

    Migration

    1. Create the new server action file core/app/[locale]/(default)/product/[slug]/_actions/get-more-images.ts with a GraphQL query to fetch additional product images with pagination.
    2. Update the product page data fetching in core/app/[locale]/(default)/product/[slug]/page-data.ts to include pageInfo (with hasNextPage and endCursor) from the images query.
    3. Update core/app/[locale]/(default)/product/[slug]/page.tsx to pass the new pagination props (pageInfo, productId, loadMoreAction) to the ProductDetail component.
    4. The ProductGallery component now accepts optional props for pagination:
      • pageInfo?: { hasNextPage: boolean; endCursor: string | null }
      • productId?: number
      • loadMoreAction?: ProductGalleryLoadMoreAction

    Due to the number of changes, it is recommended to use the PR as a reference for migration.

  • #2758 d78bc85 Thanks @Tharaae! - Add the following messages to each line item on cart page based on store inventory settings:

    • Fully/partially out-of-stock message if enabled on the store and the line item is currently out of stock
    • Ready-to-ship quantity if enabled on the store
    • Backordered quantity if enabled on the store

    Migration

    For existing Catalyst stores, to get the newly added feature, simply rebase the existing code with the new release code. The files to be rebased for this change to be applied are:

    • core/app/[locale]/(default)/cart/page-data.ts
    • core/app/[locale]/(default)/cart/page.tsx
    • core/messages/en.json
    • core/vibes/soul/sections/cart/client.tsx

Patch Changes

  • #2852 a7395f1 Thanks @chanceaclark! - Uses regular dompurify (DP) instead of isomorphic-dompurify (IDP), because IDP requires JSDOM. JSDOM doesn't work in edge-runtime environments even with nodejs compatibility. We only need it on the client anyways for the JSON-LD schema, so it doesn't need the isomorphic aspect of it. This also changes core/app/[locale]/(default)/product/[slug]/_components/product-review-schema/product-review-schema.tsx to be a client-component to enable `dompurify to work correctly.

    Migration

    1. Remove the old dependency and add the new:
    pnpm rm isomorphic-dompurify
    pnpm add dompurify -S
    1. Change the import in core/app/[locale]/(default)/product/[slug]/_components/product-review-schema/product-review-schema.tsx:
    - import DOMPurify from 'isomorphic-dompurify';
    +// eslint-disable-next-line import/no-named-as-default
    +import DOMPurify from 'dompurify';
    1. Add the 'use client'; directive to the top of core/app/[locale]/(default)/product/[slug]/_components/product-review-schema/product-review-schema.tsx.
  • #2844 74dee6e Thanks @jordanarldt! - Update forms to translate the form field validation errors

    Migration

    Due to the amount of changes, it is recommended to just use the PR as a reference for migration.

    Detailed migration steps can be found on the PR here:
    chore(catalyst): CATALYST-1267 Translate form field errors #2844

  • #2858 0633612 Thanks @jorgemoya! - Use state abbreviation instead of entityId for cart shipping form state values. The shipping API expects state abbreviations, and using entityId caused form submissions to fail. Additionally, certain US military states that share the same abbreviation (AE) are now filtered out to prevent duplicate key issues and ambiguous submissions.

    Migration steps

    Step 1: Add blacklist for states with duplicate abbreviations

    Certain US states share the same abbreviation (AE), which causes issues with the shipping API and React select dropdowns. Add a blacklist to filter these out.

    Update core/app/[locale]/(default)/cart/page.tsx:

      const countries = shippingCountries.map((country) => ({
        value: country.code,
        label: country.name,
      }));
    
    + // These US states share the same abbreviation (AE), which causes issues:
    + // 1. The shipping API uses abbreviations, so it can't distinguish between them
    + // 2. React select dropdowns require unique keys, causing duplicate key warnings
    + const blacklistedUSStates = new Set([
    +   'Armed Forces Africa',
    +   'Armed Forces Canada',
    +   'Armed Forces Middle East',
    + ]);
    
      const statesOrProvinces = shippingCountries.map((country) => ({

    Step 2: Use state abbreviation instead of entityId

    Update the state mapping to use abbreviation instead of entityId, and apply the blacklist filter for US states.

    Update core/app/[locale]/(default)/cart/page.tsx:

      const statesOrProvinces = shippingCountries.map((country) => ({
        country: country.code,
    -   states: country.statesOrProvinces.map((state) => ({
    -     value: state.entityId.toString(),
    -     label: state.name,
    -   })),
    +   states: country.statesOrProvinces
    +     .filter((state) => country.code !== 'US' || !blacklistedUSStates.has(state.name))
    +     .map((state) => ({
    +       value: state.abbreviation,
    +       label: state.name,
    +     })),
      }));
  • #2856 f5330c7 Thanks @jorgemoya! - Add canonical URLs and hreflang alternates for SEO. Pages now set alternates.canonical and alternates.languages in generateMetadata via the new getMetadataAlternates helper in core/lib/seo/canonical.ts. The helper fetches the vanity URL via GraphQL (site.settings.url.vanityUrl) and is cached per request. The default locale uses no path prefix; other locales use /{locale}/path. The root locale layout sets metadataBase to the configured vanity URL so canonical URLs resolve correctly.

    Migration steps

    Step 1: Root layout metadata base

    The root locale layout now sets metadataBase from the vanity URL fetched via GraphQL. This is already included in the RootLayoutMetadataQuery.

    Update core/app/[locale]/layout.tsx:

    + const vanityUrl = data.site.settings?.url.vanityUrl;
    +
      return {
    +   metadataBase: vanityUrl ? new URL(vanityUrl) : undefined,
        title: {

    Step 2: GraphQL fragment updates

    Add the path field to brand, blog post, and product queries so metadata can build canonical URLs.

    Update core/app/[locale]/(default)/(faceted)/brand/[slug]/page-data.ts:

      site {
        brand(entityId: $entityId) {
          name
    +     path
          seo {

    Update core/app/[locale]/(default)/blog/[blogId]/page-data.ts:

      author
      htmlBody
      name
    + path
      publishedDate {

    Update core/app/[locale]/(default)/product/[slug]/page-data.ts (in the metadata query):

      site {
        product(entityId: $entityId) {
          name
    +     path
          defaultImage {

    Step 3: Page metadata alternates

    Add the getMetadataAlternates import and set alternates in generateMetadata for each page. The function is async and must be awaited. Ensure core/lib/seo/canonical.ts exists (it is included in this release).

    Update core/app/[locale]/(default)/page.tsx (home):

    + import { Metadata } from 'next';
      import { getFormatter, getTranslations, setRequestLocale } from 'next-intl/server';
      ...
    + import { getMetadataAlternates } from '~/lib/seo/canonical';
      ...
    + export async function generateMetadata({ params }: Props): Promise<Metadata> {
    +   const { locale } = await params;
    +   return {
    +     alternates: await getMetadataAlternates({ path: '/', locale }),
    +   };
    + }
    +
      export default async function Home({ params }: Props) {

    For entity pages (product, category, brand, blog, blog post, webpage), add the import and include alternates in the existing generateMetadata return value using the entity path (or breadcrumb-derived path for category and webpage). Example for a brand page:

    + import { getMetadataAlternates } from '~/lib/seo/canonical';
      ...
      export async function generateMetadata(props: Props): Promise<Metadata> {
    -   const { slug } = await props.params;
    +   const { slug, locale } = await props.params;
        ...
        return {
          title: pageTitle || brand.name,
          description: metaDescription,
          keywords: metaKeywords ? metaKeywords.split(',') : null,
    +     alternates: await getMetadataAlternates({ path: brand.path, locale }),
        };
      }

    Step 4: Gift certificates pages

    Update core/app/[locale]/(default)/gift-certificates/page.tsx:

    + import { getMetadataAlternates } from '~/lib/seo/canonical';
      ...
      export async function generateMetadata({ params }: Props): Promise<Metadata> {
        const { locale } = await params;
        const t = await getTranslations({ locale, namespace: 'GiftCertificates' });
    
        return {
          title: t('title') || 'Gift certificates',
    +     alternates: await getMetadataAlternates({ path: '/gift-certificates', locale }),
        };
      }

    Update core/app/[locale]/(default)/gift-certificates/balance/page.tsx:

    + import { getMetadataAlternates } from '~/lib/seo/canonical';
      ...
        return {
          title: t('title') || 'Gift certificates - Check balance',
    +     alternates: await getMetadataAlternates({ path: '/gift-certificates/balance', locale }),
        };

    Add generateMetadata to core/app/[locale]/(default)/gift-certificates/purchase/page.tsx:

    + import { Metadata } from 'next';
      import { getFormatter, getTranslations } from 'next-intl/server';
      ...
    + import { getMetadataAlternates } from '~/lib/seo/canonical';
      ...
    + export async function generateMetadata({ params }: Props): Promise<Metadata> {
    +   const { locale } = await params;
    +   const t = await getTranslations({ locale, namespace: 'GiftCertificates' });
    +
    +   return {
    +     title: t('Purchase.title'),
    +     alternates: await getMetadataAlternates({ path: '/gift-certificates/purchase', locale }),
    +   };
    + }

    Step 5: Contact page

    Update core/app/[locale]/(default)/webpages/[id]/contact/page.tsx:

    + import { getMetadataAlternates } from '~/lib/seo/canonical';
      ...
      export async function generateMetadata({ params }: Props): Promise<Metadata> {
    -   const { id } = await params;
    +   const { id, locale } = await params;
        const webpage = await getWebPage(id);
        const { pageTitle, metaDescription, metaKeywords } = webpage.seo;
    
        return {
          title: pageTitle || webpage.title,
          description: metaDescription,
          keywords: metaKeywords ? metaKeywords.split(',') : null,
    +     alternates: await getMetadataAlternates({ path: webpage.path, locale }),
        };
      }

    Step 6: Public wishlist page

    Update core/app/[locale]/(default)/wishlist/[token]/page.tsx:

    + import { getMetadataAlternates } from '~/lib/seo/canonical';
      ...
      export async function generateMetadata({ params, searchParams }: Props): Promise<Metadata> {
        const { locale, token } = await params;
        ...
        return {
          title: wishlist?.name ?? t('title'),
    +     alternates: await getMetadataAlternates({ path: `/wishlist/${token}`, locale }),
        };
      }

    Step 7: Compare page

    Update core/app/[locale]/(default)/compare/page.tsx:

    + import { getMetadataAlternates } from '~/lib/seo/canonical';
      ...
      export async function generateMetadata({ params }: Props): Promise<Metadata> {
        const { locale } = await params;
        const t = await getTranslations({ locale, namespace: 'Compare' });
    
        return {
          title: t('title'),
    +     alternates: await getMetadataAlternates({ path: '/compare', locale }),
        };
      }

@github-actions github-actions bot requested a review from a team as a code owner January 29, 2026 21:58
@vercel
Copy link

vercel bot commented Jan 29, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
catalyst Ready Ready Preview, Comment Feb 11, 2026 8:12pm

Request Review

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.

0 participants