Skip to content

@bigcommerce/catalyst-core@1.7.0

Choose a tag to compare

@github-actions github-actions released this 01 Jun 17:01
3c96c47

Minor Changes

  • #2989 518d20a Thanks @jorgemoya! - Restore locale-aware lang attribute on the root <html> tag. The previous root layout hardcoded lang="en" for all locales; ownership of <html>/<body> now lives in app/[locale]/layout.tsx so lang={locale} reflects the active locale. The root app/layout.tsx is now a passthrough, and app/not-found.tsx is self-sufficient (renders its own <html>/<body>) to preserve the branded 404 for non-localized requests.

  • #2825 7b38d98 Thanks @chanceaclark! - Add GraphQL proxy to enable client-side GraphQL requests through the storefront. This proxy forwards requests from allowed clients (such as checkout-sdk-js) to the BigCommerce Storefront API using a dedicated unauthenticated storefront token for API authorization, while still passing through the customer access token for customer-specific data. No browser cookies are forwarded.

    Migration

    Step 1: Add environment variable

    Add a BIGCOMMERCE_STOREFRONT_UNAUTHENTICATED_TOKEN to your .env.local. This should be a storefront API token scoped for client-side proxy use with minimal permissions.

    Step 2: Create proxy

    Create a new file core/proxies/with-graphql-proxy.ts:

    import { NextResponse, URLPattern } from 'next/server';
    import { z } from 'zod';
    
    import { auth } from '~/auth';
    import { client } from '~/client';
    
    import { type ProxyFactory } from './compose-proxies';
    
    const ALLOWED_REQUESTERS = ['checkout-sdk-js'];
    const graphqlPathPattern = new URLPattern({ pathname: '/graphql' });
    
    const bodySchema = z.object({
      query: z.unknown(),
      variables: z.record(z.unknown()).default({}),
    });
    
    export const withGraphqlProxy: ProxyFactory = (next) => {
      return async (request, event) => {
        // Only handle /graphql path
        if (!graphqlPathPattern.test(request.nextUrl.toString())) {
          return next(request, event);
        }
    
        const requester = request.headers.get('x-catalyst-graphql-proxy-requester');
    
        // Validate required header
        if (!requester || !ALLOWED_REQUESTERS.includes(requester)) {
          return next(request, event);
        }
    
        // Only handle POST requests
        if (request.method !== 'POST') {
          return new NextResponse('Method not allowed', { status: 405 });
        }
    
        // Wrap in auth to get customer access token for customer-specific data
        return auth(async (req) => {
          try {
            // Parse incoming GraphQL request body
            const body: unknown = await req.json();
            const { query, variables } = bodySchema.parse(body);
    
            if (!query) {
              return NextResponse.json({ error: 'Missing query' }, { status: 400 });
            }
    
            // Get customer access token if authenticated
            const customerAccessToken = req.auth?.user?.customerAccessToken;
    
            // Proxy the request using the existing client with an unauthenticated storefront token
            const response = await client.fetch({
              document: query,
              variables,
              customerAccessToken,
              fetchOptions: {
                headers: {
                  Authorization: `Bearer ${process.env.BIGCOMMERCE_STOREFRONT_UNAUTHENTICATED_TOKEN}`,
                },
                next: { revalidate: 0 },
              },
            });
    
            return NextResponse.json(response);
          } catch (error) {
            // eslint-disable-next-line no-console
            console.error(error);
    
            return NextResponse.json(error, { status: 500 });
          }
          // @ts-expect-error auth() overload expects middleware return type, but we return NextResponse directly for the proxy
        })(request, event);
      };
    };

    Step 3: Register proxy

    Update core/proxy.ts to include the new proxy in the composition chain:

      import { composeProxies } from './proxies/compose-proxies';
      import { withAnalyticsCookies } from './proxies/with-analytics-cookies';
      import { withAuth } from './proxies/with-auth';
      import { withChannelId } from './proxies/with-channel-id';
    + import { withGraphqlProxy } from './proxies/with-graphql-proxy';
      import { withIntl } from './proxies/with-intl';
      import { withRoutes } from './proxies/with-routes';
    
      export const proxy = composeProxies(
        withAuth,
        withAnalyticsCookies,
        withIntl,
        withChannelId,
    +   withGraphqlProxy,
        withRoutes,
      );

    The withGraphqlProxy proxy should be placed after withChannelId and before withRoutes in the chain.

Patch Changes

  • #3002 24cc310 Thanks @bc-alexsaiannyi! - Fix cart summary Discounts row not showing manual discounts applied via the Management Checkout API

  • #2976 a85fa42 Thanks @jorgemoya! - Add X-Correlation-ID header to all GraphQL requests. Each page render gets a stable UUID that persists across all fetches within the same render, enabling easier request tracing in server logs.

  • #3021 4e44415 Thanks @mfaris9! - Default brand PLP sort to featured to honor the merchant's product sort order.

  • #2964 d00beeb Thanks @chanceaclark! - Prevent breadcrumb array mutation on cached web pages by spreading the React cache() result before reversing, and fix an off-by-one in truncateBreadcrumbs that incorrectly truncated arrays exactly at the target length. Also defer ProductReviewSchema to client-only rendering to avoid a DOMPurify SSR crash.

  • #3005 37e0a7c Thanks @mfaris9! - Fix formField.required mismatch for checkbox-group fields in DynamicForm. The schema branch was missing .optional() for non-required checkbox groups.

  • #3013 9aacfdc Thanks @chanceaclark! - Pass the customer access token through route resolution and the normal/contact webpage queries so customer-restricted web pages are accessible (and appear in navigation) for authenticated customers. The with-routes proxy is now wrapped in auth(), and webpage page-data queries switch to an uncached fetch when a customer token is present.

  • #2963 a8dd99e Thanks @chanceaclark! - Fix DynamicForm not rendering hidden field types, which caused pageEntityId to be NaN on contact form submission.

  • #3014 a792a86 Thanks @parthshahp! - TRAC-276 translate the gift certificate line item title in cart, order list, and order details. Previously the title was rendered as the raw English "{amount} Gift Certificate" string stored on the BigCommerce backend; it is now rendered from the existing Cart.GiftCertificate.giftCertificate translation key, with the amount shown in the separate price column (the order list view now populates price/totalPrice for gift certificates, which were previously empty).

  • #3008 77c8e8d Thanks @bc-svc-local! - Update translations.

  • #2959 4870221 Thanks @bc-svc-local! - Update translations.

  • #2987 e18feb6 Thanks @bc-svc-local! - Update translations.

  • #3022 c5fc1af Thanks @bc-svc-local! - Update translations.

  • Updated dependencies [f4216a6]:

    • @bigcommerce/catalyst-client@1.0.2