Skip to content

Add support for variant id product URLs via redirect - #3952

Open
fredericoo wants to merge 4 commits into
previewfrom
fb-variant-pdp-query
Open

Add support for variant id product URLs via redirect#3952
fredericoo wants to merge 4 commits into
previewfrom
fb-variant-pdp-query

Conversation

@fredericoo

@fredericoo fredericoo commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

TL;DR: Let Hydrogen product pages accept Liquid-style ?variant=<numeric id> links while keeping option params as the canonical product selection URL.

Before

Product loaders only understood option params:

/products/snowboard?Color=Red&Size=M

?variant=123 was treated like an option named variant, and template URL helpers could preserve stale variant params while switching options.

After

handleShopifyRoutes now accepts the app route templates once:

handleShopifyRoutes({
  request,
  requestContext,
  sessionManager,
  storefrontClient,
  routeTemplates,
  handlers: [cartHandlers],
});

That lets Hydrogen recognize Liquid-style product links before framework routing:

/products/snowboard?variant=123

and redirect them to the canonical option-param URL for the resolved variant.

What this changes

  • Adds a built-in handleShopifyRoutes pre-route check for product ?variant=<numeric id> URLs.
  • Adds routeTemplates to handleShopifyRoutes options so product URLs are recognized from the app's routing manifest.
  • Infers localized pathPrefix from requestContext.i18n.pathPrefix instead of asking callers to pass it separately.
  • Adds buildProductSelectionSearchParams() so templates and examples can build option-param or explicit variant links without preserving stale selection params.
  • Reserves variant in getSelectedProductOptions so loaders never treat it as a product option.
  • Restricts registered route redirect statuses to 301 | 302 | 303 | 307 | 308 and validates that at runtime for JS callers.
  • Updates the React Router and Next.js templates, framework examples, packaged skills, and docs for the new wiring.

Developer impact

Includes a minor changeset for @shopify/hydrogen.

This adds routeTemplates to handleShopifyRoutes, adds buildProductSelectionSearchParams(), and exposes ShopifyRedirectStatus. The earlier optional acceptProductVariantId handler and generic match-handler shape are no longer part of the public API for this PR.

UX impact

Buyers landing on Liquid-style product links like /products/foo?variant=123 now land on the matching Hydrogen variant instead of the default variant. In-app option navigation still uses option params as the canonical URL.

Out of scope

  • No opt-out for the built-in variant redirect in this PR.
  • No explicit cache or in-flight dedupe option for the variant lookup in this PR.

Risk

  • Valid numeric ?variant= product links now do one Storefront API lookup before redirecting.
  • Cross-product combined-listing redirects preserve unrelated params. Stale source-product option params may also survive, but loaders that filter with allowedOptionNames ignore them.

How to Test

  1. Run pnpm install && pnpm --filter hydrogen dev.
  2. Open a product page and note a valid variant id for that product.
  3. Visit /products/{handle}?variant={numericVariantId}.
  4. Confirm the request redirects to /products/{handle}?OptionName=value and the matching variant renders.
  5. Visit /products/{handle}?variant=not-a-number.
  6. Confirm the page renders normally and does not redirect.

Assisted-By: devx/e521c41d-1f69-4cf4-a15b-500550b56ab0
@fredericoo fredericoo changed the title Add variant id product URL redirects Add support for variant id product URLs via redirect Aug 14, 2026
Assisted-By: devx/55056b40-7bff-48b1-b3d3-f70d0c9b35c5
@fredericoo
fredericoo marked this pull request as ready for review August 14, 2026 13:15
@fredericoo
fredericoo requested a review from a team as a code owner August 14, 2026 13:15

@frandiox frandiox left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes sense to handle it as a redirect, but perhaps we should consider this as a core feature instead of as an optional handler? Left a comment below with more thoughts 👍

Comment thread packages/hydrogen/src/core/product/options.ts
Comment thread packages/hydrogen/src/core/request-routing/route-types.ts Outdated
Comment thread examples/hydrogen/server.ts Outdated
Comment thread examples/hydrogen/server.ts Outdated
Comment thread templates/react-router/app/root.tsx Outdated
Comment thread packages/hydrogen/src/core/request-routing/registered-routes.ts Outdated
Comment thread packages/hydrogen/src/core/product/accept-variant-id.ts Outdated
Comment thread packages/hydrogen/src/core/product/accept-variant-id.ts
Move Liquid-style variant URL redirects into handleShopifyRoutes so routeTemplates and i18n pathPrefix are configured once. Keep registered route handlers as exact-path groups, restrict redirect statuses, and update docs/templates for the new wiring.

Assisted-By: devx/ae222d2c-c26f-4eeb-8dde-5cfd0ca36c0d
@fredericoo
fredericoo marked this pull request as draft August 17, 2026 18:08
@fredericoo
fredericoo marked this pull request as ready for review August 17, 2026 18:21
@fredericoo
fredericoo marked this pull request as draft August 17, 2026 18:25
Assisted-By: devx/ae222d2c-c26f-4eeb-8dde-5cfd0ca36c0d
@fredericoo
fredericoo marked this pull request as ready for review August 19, 2026 13:31
@fredericoo
fredericoo requested a review from frandiox August 19, 2026 13:31
@fredericoo

Copy link
Copy Markdown
Contributor Author

I think it makes sense to handle it as a redirect, but perhaps we should consider this as a core feature instead of as an optional handler? Left a comment below with more thoughts 👍

it's now a core feature! not much had to be moved, it works pretty smoothly now

@frandiox

Copy link
Copy Markdown
Contributor

Having now second thoughts on this: the PR is good and is a good feature. However, it seems Liquid doesn't support the options pattern for URLs (I thought it did), only variant=<id> (and another option_values=<comma separated ids> that I think we don't need to care about for now).

Certain features like Storefront Agent currently depend on reading the variant ID from the URL to know in which product/variant we showing in the page. If we implement this via redirects, we'll be removing the id from the URL, thus removing that context.

We don't necessarily need to solve this by adding the variant id to the URL, there are also other alternatives (making SA resolve options the Hydrogen-style, or giving this variant id context in another way e.g. via window.Shopify.pageContext). But it would be nice to be aligned with Liquid on this if possible...

I wonder if there's a way to somehow keep both variant and options in the URL:

  • Even though that means two sources of truth, we could make the variant id the dominant value
  • If missing, add it via history-push when it resolves in the frontend
  • Have product handlers in the backend follow these principles as well (check variant first, then options)

@andguy95 @fredericoo Is this worth considering? Or perhaps not very interesting for headless?

@fredericoo

Copy link
Copy Markdown
Contributor Author

Having now second thoughts on this: the PR is good and is a good feature. However, it seems Liquid doesn't support the options pattern for URLs (I thought it did), only variant=<id> (and another option_values=<comma separated ids> that I think we don't need to care about for now).

Certain features like Storefront Agent currently depend on reading the variant ID from the URL to know in which product/variant we showing in the page. If we implement this via redirects, we'll be removing the id from the URL, thus removing that context.

We don't necessarily need to solve this by adding the variant id to the URL, there are also other alternatives (making SA resolve options the Hydrogen-style, or giving this variant id context in another way e.g. via window.Shopify.pageContext). But it would be nice to be aligned with Liquid on this if possible...

I wonder if there's a way to somehow keep both variant and options in the URL:

  • Even though that means two sources of truth, we could make the variant id the dominant value
  • If missing, add it via history-push when it resolves in the frontend
  • Have product handlers in the backend follow these principles as well (check variant first, then options)

@andguy95 @fredericoo Is this worth considering? Or perhaps not very interesting for headless?

looks very hacky and goes even more against the customisability aspect of hydrogen "you own your URLs"

i would push towards making the SF Agent be able to understand variant id from option values in the URL (but this requires a query), or straight off the DOM (we do have the variantId there in the add to cart button, we just dont surface it in the URL – it can be read)

@andguy95

Copy link
Copy Markdown
Collaborator

Yeah not a huge fan of having the variant be a required param to be in the URL.

We should let that decision fall back to how the users want to display it. It maybe worth having a strategy config in the API help users easily set up canonical params vs variant params.

But either case, I agree we should find a better location to expose the selected variantid for external consumers to use.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants