DEV Community

John Builds
John Builds

Posted on

We moved checkout behind a preview. The hard part was the reload.

Our onboarding used to go signup, card, then setup. You paid before you saw anything we would write for you.

This month we flipped it. Step one is now your website URL. We fetch the homepage once, write three posts for the business, and only then show the checkout step.

A few implementation notes, since the UI change was the easy part.

Every preview run is a stored row with a status. Only a run that reaches ready counts against a small per-account allowance, so a crawl that fails on a slow site costs the user nothing and they just retry.

It grants nothing. No drafts, no completed-onboarding flag. The resume logic makes sure an unpaid user cannot skip past checkout from any stored step pointer. That part needed more tests than the preview itself.

The follow-up crawl starts from the billing webhook, not the frontend. We used to ask for the website again two steps later (the field literally opened with "Still your website?"). The obvious fix was to kick off the full site crawl from the client after checkout. But our checkout confirmation has a path that tells the user to reload, and a reload drops the URL from component state. A client-side trigger would work on the happy path and silently never fire on the retry path. So the subscription-created webhook reads the stored preview row and starts the crawl server-side.

The general lesson: if a side effect has to happen after a redirect-and-return flow, anchor it to something persisted, not to whatever the page happens to remember.

I build XreplyAI, an AI social media teammate for small B2B teams: https://xreplyai.com/?utm_source=devto&utm_medium=social&utm_campaign=feature-2026-09-24

Top comments (1)

Collapse
 
mythex profile image
Mythex •

"Anchor it to something persisted" is the lesson most checkout flows learn the hard way. Two more paths worth a test on the webhook side: the webhook arriving while the preview row is still running (fast checkout, slow site), and Stripe delivering the same subscription event twice. If the handler keys the crawl on the preview id and skips when one already exists, both paths end up doing the same thing.