Build a product details page with Hydrogen
In this tutorial, you'll create a new page in your Remix app to display product data from Shopify.
By creating a product page, you can share information about a product with customers, including the product’s variants, description, and price. You’ll also be able to offer customers a way to purchase the product.
What you'll learnAnchor link to section titled "What you'll learn"
In this tutorial, you'll learn how to do the following tasks:
- Set up a new Remix route
- Query products by their handle
- Render product data on the page
- Create a product variant selector
- Make the product variant selector interactive with Remix Links and Optimistic UI
- Add a Shop Pay button
RequirementsAnchor link to section titled "Requirements"
- You've built a collection page with Hydrogen.
- You're using API version 2023-07 or higher.
Step 1: Create a product routeAnchor link to section titled "Step 1: Create a product route"
To begin building your product page, create a file called
app/routes/products.$handle.jsx, and add the following code.
Ensure the page renders by clicking through to the Hoodie.
Step 2: Setup the Remix loader to get product dataAnchor link to section titled "Step 2: Setup the Remix loader to get product data"
Loaders are functions that retrieve the data that's needed to render the page.
Create the loaderAnchor link to section titled "Create the loader"
Create a loader function by adding the following code to the top of your
products.$handle.jsx route. This code example retrieves the URL
handle variable from the
params that are passed to the loader function, and returns sample JSON that you can use in the JSX component.
Render the loader dataAnchor link to section titled "Render the loader data"
Now that you have a loader, you can get the loader's data in your JSX component with the
useLoaderData() hook. Import the hook and render the handle. Your complete
products.$handle.jsx file should look like this.
Reload your page to see the URL handle render on the page.
Step 3: Query the Storefront APIAnchor link to section titled "Step 3: Query the Storefront API"
loader function uses the
params object to get URL data. Remix loaders also include the
context objects. Shopify's storefront query function exists in the
context object. You can use the following steps to set up a GraphQL query and connect it to your storefront
Create a GraphQL stringAnchor link to section titled "Create a GraphQL string"
Add the following query to the bottom of your product route. This simple query uses the
$handle variable to retrieve the data for a single product.
Execute the query in the Remix loaderAnchor link to section titled "Execute the query in the Remix loader"
Now you can combine your handle and GraphQL query string with a storefront
query() function. Update your loader:
Log the product dataAnchor link to section titled "Log the product data"
ProductHandle() component to destructure and log product data:
You should see product details in your developer console.
Step 4: Display title, description, and product imagesAnchor link to section titled "Step 4: Display title, description, and product images"
Now that you're familiar with querying product data, you can start building out the page. First, update your GraphQL query to get more product data.
Update the GraphQL queryAnchor link to section titled "Update the GraphQL query"
Replace your existing
PRODUCT_QUERY with the following query to fetch more product data, including descriptive details, images, and options.
Render the title, vendor and description on the page:
handle object for your SEO component.
Render the product imageAnchor link to section titled "Render the product image"
Image component at the top of your product route. This component will render a responsive image.
<h2>TODO Product Image</h2> in your
ProductHandle() function with the
<Image> component and pass along your featured image.
You should have the featured image, title, vendor, and description rendering in a two-column layout.
Step 5: Render the product variant optionsAnchor link to section titled "Step 5: Render the product variant options"
Remix loads a full page when you navigate to it. For each additional URL change, React Router will handle page updates. The end result is a website that feels like an app, without all of the hassle.
Remix uses the URL to store state. This means that any interaction that you build for the product options selector should also be reflected in the URL. You'll use Remix
<Link> components to help you build these interactive pieces.
The option selector works as follows:
- User clicks on option
- Remix updates the URL
- Remix runs the
loader()function with the new URL
- The loader function executes a
- Remix returns the new data as JSON
- React receives an update from the
useLoaderData()hook and makes the necessary updates to the page.
You will need to make two updates for the options selector to work.
- Render the options as
- Update the
storefront.query()to accept the new options URL parameters
Render the product options linksAnchor link to section titled "Render the product options links"
Create a new component to render the options as links. Create the file
app/components/ProductOptions.jsx. Add in the following code.
Now you can add this component to your
ProductHandle() function in
At the top of the file, import the new component.
Right before the description block, render the component.
Reload your page and click on some links. You should see the URL change, but the page doesn't reload. This is React Router taking control and loading your updated state.
You still need to update the loader function to recognize our URL parameters and update the data, which is covered in the next section.
Update the LoaderAnchor link to section titled "Update the Loader"
As well as
context, the loader also has access to the
request object. You'll need the
searchParams from the request URL.
Use the following code to update the
loader function in your product route to pull in the
request and format the options data for the Storefront API.
This updated code takes the search params and creates a
selectedOptions array of objects that's passed to the
Now, you can update your
PRODUCT_QUERY to get a selected variant from the
variantBySelectedOptions returns the selected variant based on the option values that you retrieved from the URL. You can test the return by adding a line below
<ProductOptions/> to print out the selected variant ID:
Click on some options to see the selected variant ID change. After you have verified, you can remove the selected variant line of code.
Right now you can't visually see which options are selected. You will fix this in the next section.
Display the selected variant's imageAnchor link to section titled "Display the selected variant's image"
Update the product image to display the selected variant's image (if available).
Update the product options UIAnchor link to section titled "Update the product options UI"
URL query parameters hold your state, so you can refer to these parameters to determine the selected option. You can read these parameters with Remix's
useSearchParams() hook. Update your logic so that it loops over each option to add a dark underline to selected
Optimistic UIAnchor link to section titled "Optimistic UI"
Now you have an option selector that responds to URL changes, but the link underline doesn't update immediately.
This delay happens because Remix is responding to the URL change, running the
loader() function, and passing that data down to the React component. Even when all servers and APIs are fast, this process isn't instant. Because you know that the selection will usually be successful, you can update the UI without waiting for a server response. Remix's Optimistic UI pattern is perfect for this use case. Next you'll add the
useNavigation() hook and update the logic that reads the search parameters.
useNavigationat the top of your
Insert the following code at the top of your
Reload the page and make some option selections. The image changes and the underline selections are now instant, even when the loader is still working to update the data.
Here is your complete
Step 6: Set a default variantAnchor link to section titled "Step 6: Set a default variant"
You might notice that a fresh product page has no options selected. You can update your loader to use a default variant so there is always an orderable product available on the page.
PRODUCT_QUERY already gets the first variant for you with the
variants(first: 1) query filter. You can use that data to update the logic in your
loader() function. You'll need to make updates in 4 places:
- Create a new
selectedVariantvalue in the loader
- Destructure it from
- Pass the
- Update the logic in
ProductOptionsto set the selected default value
Update the loaderAnchor link to section titled "Update the loader"
Add the following code to the bottom of the
loader function, replacing the
Update the Anchor link to section titled "Update the useLoaderData() hook"
selectedVariant to the
useLoaderData() destructured object:
Pass the variant to Anchor link to section titled "Pass the variant to ProductOptions"
<ProductOptions> component to accept a
Update Anchor link to section titled "Update ProductOptions"
Replace the top portion of
ProductOptions with the following code:
The new logic does the following:
- Get the
selectedVariantfrom the component props
- On render, create a
- Clone the existing parameters
- If the parameters don't already include a selected value for the selected variant, then add it to the cloned parameters
paramsWithDefaultsas the fallback when you create the
Test the new logic by visiting http://localhost:3000/products/snowboard. Default option values should be selected on page load.
This logic doesn't override a user selection, but it pre-populates selected options based on the first variant returned from the Storefront API.
Step 7: Add a Shop Pay ButtonAnchor link to section titled "Step 7: Add a Shop Pay Button"
You've already created a page where customers can view and select your product. To add a Shop Pay button, you can refer to the Hydrogen React components for pre-built commerce primitives.
ShopPay components to simplify your development experience.
Get the Shop DomainAnchor link to section titled "Get the Shop Domain"
To use the Shop Pay button, you'll need to get the shop's primary domain from the Storefront API.
PRODUCT_QUERYto also fetch the shop's primary domain URL.
shopobject from the response.
shopobject in the
Render the Shop Pay ButtonAnchor link to section titled "Render the Shop Pay Button"
products.$handle.jsxfile, import the Hydrogen React components to render pricing and the Shop Pay button:
<ProductOptions>, render the price and the Shop Pay button:
The pricing should now show for the selected variant, and you can jump directly to a Shop Pay checkout (if the store has it activated).
The product page now renders all of the details for a product and its variants. It also includes a button to purchase the product.
Implement an Add to cart button, enabling customers to choose products to purchase before entering the payment process.