Check your storefront before launch
Type checks and a successful build don't prove that Hydrogen is wired correctly. Hydrogen's request handlers run around your framework's router, so a storefront can build cleanly and still return a 404 for the cart API. Use this checklist after you create or change a storefront, and before each production deployment.
Run the checks against your framework's development server first, and then repeat the request handler checks against a production build. If a check fails, then fix the storefront and run that check again.
The curl commands on this page use <port> for your app's port. Vercel Shop and other Next.js apps run on port 3000, and the React Router template runs on port 5173 in development and 4173 in preview.
Anchor to Static checksStatic checks
Run your project's scripts in this order:
- Your formatter, if your project has one.
- Your lint, type check, or
checkscript. hydrogen gql check, if you changed anygql()documents. The React Router template runs it as part oftypecheck.build.test.- Your format check, if it's separate from the formatter.
Anchor to Request handlersRequest handlers
Confirm that handleShopifyRoutes() and handleShopifyRedirects() run where your framework expects them. To learn how to wire them, refer to Request handlers.
-
Send a query to the Storefront API proxy. Replace
{api-version}with the Storefront API version that your app uses:Terminal
curl -i -X POST http://localhost:<port>/api/{api-version}/graphql.json \-H "content-type: application/json" \-H "X-Shopify-Storefront-Access-Token: <public-token>" \-d '{"query":"{ shop { name } }"}'The response is Storefront API JSON, not your framework's 404 page. The proxy forwards the token header from the incoming request. It doesn't add a token from your server config.
-
Request the cart API without a cart cookie:
Terminal
curl -i http://localhost:<port>/api/cartThe response is JSON with the body
{"cart":null}, not a 404. -
Request
/admin:Terminal
curl -i http://localhost:<port>/adminThe response redirects to
/adminon your store's domain. -
Request a path that doesn't exist:
Terminal
curl -i http://localhost:<port>/this-does-not-existThe response is your framework's 404 page, unless your store has a URL redirect for that path.
handleShopifyRoutes() answers /api/cart and predictive search requests before your router runs. A missing route file for these paths doesn't mean the handler is missing.
Request every link in your header and footer directly:
Terminal
Every link returns 200. A 404 means that the link points to a route that your app doesn't render, or to a handle that your store doesn't have.
A common failure is /collections/all. The Online Store's "all" collection doesn't exist in the Storefront API, so link to it only if your store has a collection with the handle all.
Anchor to Customer accountsCustomer accounts
-
Request the login route:
Terminal
curl -i http://localhost:<port>/account/loginThe response redirects, and its
locationheader points to Shopify's hosted login page. It isn't a 404 or a 500. -
Open
/accountwithout signing in. The page shows a signed-out state. -
Check that the sign-out control is a native form:
<form method="post" action="/account/logout">. The logout handler accepts only same-origin POST requests, and returns a redirect that only a native form submission follows.
Completing a real sign-in requires an HTTPS origin that's registered as a callback URL for your Customer Account API client. If your local origin isn't registered, then check only the signed-out behavior. For more information, refer to Test sign-in locally with HTTPS.
Anchor to Product pagesProduct pages
- The server renders the title, images, price, variant options, add to cart form, and related products when they're available.
- Reloading a URL with option parameters selects the expected variant.
- Selecting an option of the same product updates the URL without scrolling to the top of the page.
- Selecting a combined listing option navigates to that product's page.
- Sold-out option values stay selectable and show that they're sold out.
- Option combinations that don't exist are disabled.
- The add to cart button is enabled only when
canAddToCart(product, options)returnstrue. - The Shop Pay button renders only when a variant is selected, and it's disabled when the add to cart button is disabled or pending.
Anchor to CartCart
/cartworks with JavaScript turned off, and your footer includes a/cartlink.- The header cart button is a link to
/cart. After hydration, it opens the cart drawer withshowModal(). window.Shopify.actions.openCart()opens the cart drawer.- Adding a product through your storefront's add to cart UI sets the cart cookie in a
Set-Cookieheader, and keeps any other response headers that your cart action sets. Test through your real UI or cart action, not a handcrafted request. - If your product page opens the cart drawer after a product is added, then the drawer opens after a successful submission.
- Each line item form includes a hidden
setaction, alineIdfor that line, and an editablequantity. - Pressing Enter in a quantity field submits a
setaction. - Clicking a quantity button several times quickly settles on the final quantity.
- Line, discount code, note, attribute, network, and cart errors appear next to the part of the cart they apply to.
- Saving a cart attribute submits the full list of attributes, keeps attributes that you didn't change, and shows a saving state. Submitting no attribute fields clears the list.
- Totals come from the Storefront API, and they show a pending state while a cart change is in progress.
For more information about the cart, refer to Cart.
Anchor to Collections and searchCollections and search
- Collection filters and sort options update the URL and the product grid.
- Checkbox filters and minimum and maximum price fields render, apply, and persist after a reload.
- Reloading a filtered URL server-renders the same filters and sort order.
- Removing an active filter removes only that filter, and keeps other URL parameters.
- Search filters keep the
qparameter. - Filter forms submit with
GETwhen JavaScript is turned off. - Using the browser's back and forward buttons doesn't leave the product grid in a loading state.
Anchor to AnalyticsAnalytics
- A page view event fires on the first load and on every client-side navigation.
- Product, collection, search, and cart view events fire once each time the route's data changes.
- Cart tracking is set up once, with
trackCartAnalytics(cartStore), or withuseCartAnalytics()in React and Vue. Cart events fire when the cart's data changes. - No browser code reads private environment variables.
- Analytics destinations receive events only after the customer's Customer Privacy settings allow analytics.
For more information, refer to Analytics and consent.
Anchor to Markets and moneyMarkets and money
- Storefront API queries that return market-specific data declare
$countryand$languagevariables and use the@inContextdirective. - Prices render from Storefront API money fields with
formatMoney(). - No code in the browser calculates subtotals or totals.
For more information, refer to Markets.
Anchor to Production buildProduction build
Development-only middleware and development export conditions can hide wiring that's broken in production. Build and run the production bundle:
Next.js
npm run build
npm run startReact Router
npm run previewThen repeat the request handler checks against the production server. The React Router template's preview script runs the built Worker in MiniOxygen, whose runtime can differ from Oxygen's. For more information about deploying, refer to About deploying Hydrogen storefronts.