Skip to content
Draft
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
5 changes: 5 additions & 0 deletions .changeset/cute-spies-talk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@bigcommerce/catalyst-core": minor
---

Use optimistic checkout redirect URL loading strategy to improve checkout load time
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
'use server';

import { getSessionCustomerAccessToken } from '~/auth';
import { getChannelIdFromLocale } from '~/channels.config';
import { client } from '~/client';
import { graphql } from '~/client/graphql';
import { getVisitIdCookie, getVisitorIdCookie } from '~/lib/analytics/bigcommerce';

const CheckoutRedirectMutation = graphql(`
mutation CheckoutRedirectMutation($cartId: String!, $visitId: UUID, $visitorId: UUID) {
cart {
createCartRedirectUrls(
input: { cartEntityId: $cartId, visitId: $visitId, visitorId: $visitorId }
) {
errors {
... on NotFoundError {
__typename
}
}
redirectUrls {
redirectedCheckoutUrl
}
}
}
}
`);

export interface GenerateCheckoutUrlResult {
url: string | null;
error?: string;
}

export async function generateCheckoutUrl(
cartId: string,
locale: string,
): Promise<GenerateCheckoutUrlResult> {
try {
const customerAccessToken = await getSessionCustomerAccessToken();
const channelId = getChannelIdFromLocale(locale);
const visitId = await getVisitIdCookie();
const visitorId = await getVisitorIdCookie();

const { data } = await client.fetch({
document: CheckoutRedirectMutation,
variables: { cartId, visitId, visitorId },
fetchOptions: { cache: 'no-store' },
customerAccessToken,
channelId,
});

if (
data.cart.createCartRedirectUrls.errors.length > 0 ||
!data.cart.createCartRedirectUrls.redirectUrls
) {
return { url: null, error: 'Failed to generate checkout URL' };
}

return { url: data.cart.createCartRedirectUrls.redirectUrls.redirectedCheckoutUrl };
} catch (error) {
// eslint-disable-next-line no-console
console.error('Error generating checkout URL:', error);

return { url: null, error: 'Failed to generate checkout URL' };
}
}
55 changes: 55 additions & 0 deletions core/app/[locale]/(default)/cart/_components/optimized-cart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
'use client';

import { SubmissionResult } from '@conform-to/react';
import { useEffect, useState } from 'react';

import {
CartClient as CartComponent,
CartLineItem,
CartProps,
} from '@/vibes/soul/sections/cart/client';
import { useRouter } from '~/i18n/routing';
import { isCheckoutUrlValid } from '~/lib/checkout-url-utils';

type CheckoutAction = (
lastResult: SubmissionResult | null,
formData: FormData,
) => Promise<SubmissionResult | null>;

interface OptimizedCartProps<LineItem extends CartLineItem> {
preGeneratedUrl: string | null;
fallbackAction: CheckoutAction;
cartProps: CartProps<LineItem>;
}

export function OptimizedCart<LineItem extends CartLineItem>({
preGeneratedUrl,
fallbackAction,
cartProps,
}: OptimizedCartProps<LineItem>) {
const router = useRouter();
const [checkoutUrl, setCheckoutUrl] = useState<string | null>(preGeneratedUrl);
const [pageLoadTime] = useState(() => Date.now()); // Capture when this component first mounted

// Update the stored URL if a new one is provided
useEffect(() => {
setCheckoutUrl(preGeneratedUrl);
}, [preGeneratedUrl]);

// Custom action that checks URL validity before redirecting
const optimizedCheckoutAction: CheckoutAction = async (lastResult, formData) => {
// Check if we have a valid pre-generated URL (accounting for clock skew and page load time)
if (checkoutUrl && isCheckoutUrlValid(checkoutUrl, pageLoadTime)) {
// Direct client-side redirect for better performance
router.push(checkoutUrl);

// Return null to prevent further processing
return null;
}

// Fall back to the original server action if URL is invalid or missing
return fallbackAction(lastResult, formData);
};

return <CartComponent {...cartProps} checkoutAction={optimizedCheckoutAction} />;
}
Loading
Loading