Skip to content

Version Packages (canary)#2927

Merged
jorgemoya merged 1 commit intocanaryfrom
changeset-release/canary
Mar 16, 2026
Merged

Version Packages (canary)#2927
jorgemoya merged 1 commit intocanaryfrom
changeset-release/canary

Conversation

@github-actions
Copy link
Contributor

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.6.0

Minor Changes

  • #2896 fc84210 Thanks @jamesqquick! - Add reCAPTCHA v2 support to storefront forms. The reCAPTCHA widget is rendered on the registration, contact, and product review forms when enabled in the BigCommerce admin. All validation and error handling is performed server-side in the corresponding form actions. The token is read from the native g-recaptcha-response field that the widget injects into the form, eliminating the need for manual token extraction on the client.

    Migration steps

    Step 1: Install dependencies

    Add react-google-recaptcha and its type definitions:

    pnpm add react-google-recaptcha
    pnpm add -D @types/react-google-recaptcha

    Step 2: Add the reCAPTCHA server library

    Create core/lib/recaptcha/constants.ts:

    export interface ReCaptchaSettings {
      isEnabledOnStorefront: boolean;
      siteKey: string;
    }
    
    export const RECAPTCHA_TOKEN_FORM_KEY = 'g-recaptcha-response';

    Create core/lib/recaptcha.ts with the server-side helpers for fetching reCAPTCHA settings, extracting the token from form data, and asserting the token is present. See the file in this release for the full implementation.

    Step 3: Add reCAPTCHA translation strings

    Update core/messages/en.json to add the recaptchaRequired message in each form namespace:

      "Auth": {
        "Register": {
    +     "recaptchaRequired": "Please complete the reCAPTCHA verification.",
      "Product": {
        "Reviews": {
          "Form": {
    +       "recaptchaRequired": "Please complete the reCAPTCHA verification.",
      "WebPages": {
        "ContactUs": {
          "Form": {
    +       "recaptchaRequired": "Please complete the reCAPTCHA verification.",
      "Form": {
    +   "recaptchaRequired": "Please complete the reCAPTCHA verification.",

    Step 4: Update GraphQL mutations to accept reCAPTCHA token

    Update core/app/[locale]/(default)/(auth)/register/_actions/register-customer.ts:

    + import { assertRecaptchaTokenPresent, getRecaptchaFromForm } from '~/lib/recaptcha';
      ...
      const RegisterCustomerMutation = graphql(`
    -   mutation RegisterCustomerMutation($input: RegisterCustomerInput!) {
    +   mutation RegisterCustomerMutation(
    +     $input: RegisterCustomerInput!
    +     $reCaptchaV2: ReCaptchaV2Input
    +   ) {
          customer {
    -       registerCustomer(input: $input) {
    +       registerCustomer(input: $input, reCaptchaV2: $reCaptchaV2) {

    Update core/app/[locale]/(default)/product/[slug]/_actions/submit-review.ts:

    + import { assertRecaptchaTokenPresent, getRecaptchaFromForm } from '~/lib/recaptcha';
      ...
      const AddProductReviewMutation = graphql(`
    -   mutation AddProductReviewMutation($input: AddProductReviewInput!) {
    +   mutation AddProductReviewMutation(
    +     $input: AddProductReviewInput!
    +     $reCaptchaV2: ReCaptchaV2Input
    +   ) {
          catalog {
    -       addProductReview(input: $input) {
    +       addProductReview(input: $input, reCaptchaV2: $reCaptchaV2) {

    Update core/app/[locale]/(default)/webpages/[id]/contact/_actions/submit-contact-form.ts:

    + import { assertRecaptchaTokenPresent, getRecaptchaFromForm } from '~/lib/recaptcha';
      ...
      const SubmitContactUsMutation = graphql(`
    -   mutation SubmitContactUsMutation($input: SubmitContactUsInput!) {
    -     submitContactUs(input: $input) {
    +   mutation SubmitContactUsMutation($input: SubmitContactUsInput!, $reCaptchaV2: ReCaptchaV2Input) {
    +     submitContactUs(input: $input, reCaptchaV2: $reCaptchaV2) {

    Step 5: Add server-side reCAPTCHA validation to form actions

    In each of the three server actions above, add the validation block after the parseWithZod check and pass the token to the GraphQL mutation. For example in register-customer.ts:

    +   const { siteKey, token } = await getRecaptchaFromForm(formData);
    +   const recaptchaValidation = assertRecaptchaTokenPresent(siteKey, token, t('recaptchaRequired'));
    +
    +   if (!recaptchaValidation.success) {
    +     return {
    +       lastResult: submission.reply({ formErrors: recaptchaValidation.formErrors }),
    +     };
    +   }
        ...
        const response = await client.fetch({
          document: RegisterCustomerMutation,
          variables: {
            input,
    +       reCaptchaV2:
    +         recaptchaValidation.token != null ? { token: recaptchaValidation.token } : undefined,
          },

    Apply the same pattern to submit-review.ts and submit-contact-form.ts.

    Step 6: Pass recaptchaSiteKey to form components

    Fetch the site key in each page and pass it down through the component tree.

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

    + import { getRecaptchaSiteKey } from '~/lib/recaptcha';
      ...
    + const recaptchaSiteKey = await getRecaptchaSiteKey();
      ...
      <DynamicFormSection
    +   recaptchaSiteKey={recaptchaSiteKey}

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

    + import { getRecaptchaSiteKey } from '~/lib/recaptcha';
      ...
    - const { product: baseProduct, settings } = await getProduct(productId, customerAccessToken);
    + const [{ product: baseProduct, settings }, recaptchaSiteKey] = await Promise.all([
    +   getProduct(productId, customerAccessToken),
    +   getRecaptchaSiteKey(),
    + ]);
      ...
      <ProductDetail
    +   recaptchaSiteKey={recaptchaSiteKey}
      ...
      <Reviews
    +   recaptchaSiteKey={recaptchaSiteKey}

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

    + import { getRecaptchaSiteKey } from '~/lib/recaptcha';
      ...
    + const recaptchaSiteKey = await getRecaptchaSiteKey();
      ...
      <DynamicForm
    +   recaptchaSiteKey={recaptchaSiteKey}

    Step 7: Render the reCAPTCHA widget in form components

    Update core/vibes/soul/form/dynamic-form/index.tsx:

    + import RecaptchaWidget from 'react-google-recaptcha';
      ...
      export interface DynamicFormProps<F extends Field> {
    +   recaptchaSiteKey?: string;
      }
      ...
    +         {recaptchaSiteKey ? <RecaptchaWidget sitekey={recaptchaSiteKey} /> : null}

    Update core/vibes/soul/sections/reviews/review-form.tsx:

    + import RecaptchaWidget from 'react-google-recaptcha';
      ...
      interface Props {
    +   recaptchaSiteKey?: string;
      }
      ...
    +           {recaptchaSiteKey ? (
    +             <div>
    +               <RecaptchaWidget sitekey={recaptchaSiteKey} />
    +             </div>
    +           ) : null}

    Step 8: Thread recaptchaSiteKey through intermediate components

    Add the recaptchaSiteKey?: string prop and pass it through in:

    • core/vibes/soul/sections/dynamic-form-section/index.tsx
    • core/vibes/soul/sections/product-detail/index.tsx
    • core/vibes/soul/sections/reviews/index.tsx
    • core/app/[locale]/(default)/product/[slug]/_components/reviews.tsx

    Each of these accepts the prop and forwards it to the form component that renders the widget.

Patch Changes

@github-actions github-actions bot requested a review from a team as a code owner March 16, 2026 20:40
@vercel
Copy link

vercel bot commented Mar 16, 2026

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

Project Deployment Actions Updated (UTC)
catalyst Ready Ready Preview, Comment Mar 16, 2026 9:09pm

Request Review

@github-actions
Copy link
Contributor Author

github-actions bot commented Mar 16, 2026

Unlighthouse Performance Comparison — Vercel

Comparing PR preview deployment Unlighthouse scores vs production Unlighthouse scores.

Summary Score

Aggregate score across all categories as reported by Unlighthouse.

Prod Desktop Prod Mobile Preview Desktop Preview Mobile
Score 92 94 91 95

Category Scores

Category Prod Desktop Prod Mobile Preview Desktop Preview Mobile
Performance 78 94 77 96
Accessibility 95 95 95 95
Best Practices 100 100 95 100
SEO 100 100 100 100

Core Web Vitals

Metric Prod Desktop Prod Mobile Preview Desktop Preview Mobile
LCP 3.5 s 3.1 s 3.4 s 2.7 s
CLS 0 0 0 0
FCP 1.1 s 1.2 s 1.2 s 1.2 s
TBT 0 ms 10 ms 10 ms 30 ms
Max Potential FID 50 ms 70 ms 70 ms 100 ms
Time to Interactive 3.5 s 3.6 s 4.3 s 3.8 s

Full Unlighthouse report →

@github-actions
Copy link
Contributor Author

Bundle Size Report

Comparing against baseline from 0d5763e (2026-03-16).

No bundle size changes detected.

@jorgemoya jorgemoya added this pull request to the merge queue Mar 16, 2026
Merged via the queue into canary with commit a00b864 Mar 16, 2026
17 of 18 checks passed
@jorgemoya jorgemoya deleted the changeset-release/canary branch March 16, 2026 21:22
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.

1 participant