Skip to content

fix(next-config): redirect Magento account URLs to the GraphCommerce routes - #2660

Merged
paales merged 2 commits into
canaryfrom
redirect-magento-customer-account-urls
Aug 6, 2026
Merged

fix(next-config): redirect Magento account URLs to the GraphCommerce routes#2660
paales merged 2 commits into
canaryfrom
redirect-magento-customer-account-urls

Conversation

@paales

@paales paales commented Aug 6, 2026

Copy link
Copy Markdown
Member

Written by Claude Code:

Magento builds its own frontend URLs from base_link_url. On a headless setup that value points at the GraphCommerce storefront, so every stock transactional email ships a link GraphCommerce cannot serve:

$ grep -rEho "'[a-z_]+/[a-z_]+/?[a-zA-Z_]*/?'" $(find vendor -path '*/email/*.html')
  48 'customer/account/'
  10 'customer/account/createPassword/'
   2 'customer/account/createPassword'
   2 'customer/account/confirm/'

createPassword and confirm are covered — all three examples ship pages/customer/account/{confirm,createPassword} (they are scaffolded from the examples; @graphcommerce/magento-customer has no copy/ directory). customer/account/ is not, and it is the single most common link in the whole email set ("Sign in to your account", in the footer of every order/invoice/shipment/credit-memo mail). It 404s.

There used to be exactly this redirect, added in #1936 and moved into withGraphCommerce shortly after. It was dropped as collateral in "Remove redirects for /product/$type/[url] routes" (2c79a4c) — that commit removed the whole redirects() block, and the /customer/account entry happened to live in the same array. This restores it and adds the neighbouring URLs that Magento also emits.

The list

Magento URL GraphCommerce route Where it comes from
/customer/account /account every stock email template
/customer/account/index /account Magento's canonical alias for the dashboard
/customer/account/login /account/signin 302 Location on gated Magento routes — magento-product-downloadable's copy pages already sniff for this string
/customer/account/create /account/signin registration links; /account/signin renders both sign-in and sign-up
/sales/order/history /account/orders Magento's "My Orders"

Why exact matches, not :path*

A redirects() entry is evaluated before the filesystem routes, so redirecting a path that has a page makes that page unreachable. GraphCommerce serves exactly two paths under /customer/: confirm and createPassword — the two carrying Magento's one-shot confirmation key and reset rp_token. A catch-all would break account confirmation and password reset outright, so every entry stays an exact source. Note that /customer/account/create is a distinct route from /customer/account/createPassword and does not cover it.

Dropped after review (a3a45d2): /customer/account/forgotpassword was in the first revision. It shadows nothing — there is no forgotpassword page anywhere in the examples — but it appears in zero email templates, and Magento's reset token rides on createPassword, not on it. No evidence, so it's gone.

Notes

  • Projects that already define their own redirects() keep working: withGraphCommerce awaits nextConfig.redirects() first and appends, and Next.js takes the first match, so a project override wins.
  • dist/index.js is updated alongside src/ per the convention in this package.

🤖 Generated with Claude Code

…routes

Magento renders links from base_link_url, which on a headless setup points
at the GraphCommerce storefront. Every stock email template contains a
customer/account/ link ("Sign in to your account") and gated Magento routes
302 to customer/account/login — GraphCommerce serves none of those, so
customers landed on a 404.

withGraphCommerce now adds permanent redirects for /customer/account,
/customer/account/index, /customer/account/login, /customer/account/create,
/customer/account/forgotpassword and /sales/order/history. They are exact
matches on purpose: redirects run before the filesystem routes, so a
/customer/account/:path* catch-all would shadow the
pages/customer/account/{confirm,createPassword} routes that
@graphcommerce/magento-customer copies into the project.

This restores the /customer/account redirect that was dropped as collateral
in "Remove redirects for /product/$type/[url] routes".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Aug 6, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: a3a45d2

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 86 packages
Name Type
@graphcommerce/next-config Patch
@graphcommerce/hygraph-dynamic-rows-ui Patch
@graphcommerce/framer-scroller-example Patch
@graphcommerce/magento-graphcms Patch
@graphcommerce/magento-open-source Patch
@graphcommerce/magento-storyblok Patch
@graphcommerce/docs Patch
@graphcommerce/browserslist-config-pwa Patch
@graphcommerce/changeset-changelog Patch
@graphcommerce/eslint-config-pwa Patch
@graphcommerce/graphql-codegen-markdown-docs Patch
@graphcommerce/graphql-codegen-near-operation-file Patch
@graphcommerce/graphql-codegen-relay-optimizer-plugin Patch
@graphcommerce/misc Patch
@graphcommerce/prettier-config-pwa Patch
@graphcommerce/typescript-config-pwa Patch
@graphcommerce/address-fields-nl Patch
@graphcommerce/algolia-categories Patch
@graphcommerce/algolia-insights Patch
@graphcommerce/algolia-personalization Patch
@graphcommerce/algolia-products Patch
@graphcommerce/algolia-recommend Patch
@graphcommerce/algolia-search Patch
@graphcommerce/cli Patch
@graphcommerce/demo-magento-graphcommerce Patch
@graphcommerce/ecommerce-ui Patch
@graphcommerce/framer-next-pages Patch
@graphcommerce/framer-scroller Patch
@graphcommerce/framer-utils Patch
@graphcommerce/google-datalayer Patch
@graphcommerce/google-playstore Patch
@graphcommerce/googleanalytics Patch
@graphcommerce/googlerecaptcha Patch
@graphcommerce/googletagmanager Patch
@graphcommerce/graphcms-ui Patch
@graphcommerce/graphql-mesh Patch
@graphcommerce/graphql Patch
@graphcommerce/hygraph-cli Patch
@graphcommerce/hygraph-dynamic-rows Patch
@graphcommerce/hygraph-ui Patch
@graphcommerce/image Patch
@graphcommerce/lingui-next Patch
@graphcommerce/magento-cart-checkout Patch
@graphcommerce/magento-cart-coupon Patch
@graphcommerce/magento-cart-email Patch
@graphcommerce/magento-cart-items Patch
@graphcommerce/magento-cart-payment-method Patch
@graphcommerce/magento-cart-pickup Patch
@graphcommerce/magento-cart-shipping-address Patch
@graphcommerce/magento-cart-shipping-method Patch
@graphcommerce/magento-cart Patch
@graphcommerce/magento-category Patch
@graphcommerce/magento-cms Patch
@graphcommerce/magento-compare Patch
@graphcommerce/magento-customer Patch
@graphcommerce/magento-graphql-rest Patch
@graphcommerce/magento-graphql Patch
@graphcommerce/magento-newsletter Patch
@graphcommerce/magento-payment-adyen Patch
@graphcommerce/magento-payment-afterpay Patch
@graphcommerce/magento-payment-braintree Patch
@graphcommerce/magento-payment-included Patch
@graphcommerce/magento-payment-klarna Patch
@graphcommerce/magento-payment-multisafepay Patch
@graphcommerce/magento-payment-paypal Patch
@graphcommerce/magento-payment-tokens Patch
@graphcommerce/magento-product-bundle Patch
@graphcommerce/magento-product-configurable Patch
@graphcommerce/magento-product-downloadable Patch
@graphcommerce/magento-product-grouped Patch
@graphcommerce/magento-product-simple Patch
@graphcommerce/magento-product-virtual Patch
@graphcommerce/magento-product Patch
@graphcommerce/magento-recently-viewed-products Patch
@graphcommerce/magento-review Patch
@graphcommerce/magento-search-overlay Patch
@graphcommerce/magento-search Patch
@graphcommerce/magento-store Patch
@graphcommerce/magento-wishlist Patch
@graphcommerce/mollie-magento-payment Patch
@graphcommerce/next-ui Patch
@graphcommerce/react-hook-form Patch
@graphcommerce/service-worker Patch
@graphcommerce/storyblok-ui Patch
@graphcommerce/framer-next-pages-example Patch
@graphcommerce/image-example Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercel Bot commented Aug 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
graphcommerce-246 Ready Ready Preview Aug 6, 2026 12:05pm
graphcommerce-247 Error Error Aug 6, 2026 12:05pm
graphcommerce-hygraph-dynamic-rows-ui Ready Ready Preview Aug 6, 2026 12:05pm
graphcommerce-open-source Ready Ready Preview Aug 6, 2026 12:05pm
graphcommerce-storyblok Ready Ready Preview Aug 6, 2026 12:05pm

Request Review

Comment thread packagesDev/next-config/dist/index.js Outdated
source: "/customer/account/forgotpassword",
destination: "/account/forgot-password",
permanent: true
},

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I think this is incorrect? We already have a path for this in the examples? As the forgotPassword also carries a token etc.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Written by Claude Code:

You're right that it should go — dropped in a3a45d2. The reasoning is slightly different from yours though, and the difference matters for the entries I kept, so let me lay out what I actually found.

There is no forgotpassword page. Full inventory of what GraphCommerce serves under /customer/, across all three examples:

examples/magento-graphcms/pages/customer/account/confirm.tsx
examples/magento-graphcms/pages/customer/account/createPassword/index.tsx
examples/magento-open-source/pages/customer/account/confirm.tsx
examples/magento-open-source/pages/customer/account/createPassword.tsx
examples/magento-storyblok/pages/customer/account/confirm.tsx
examples/magento-storyblok/pages/customer/account/createPassword.tsx

That's it — confirm and createPassword. /account/forgot-password exists, but that's the GraphCommerce route and it was the destination of the redirect, not the source. (Also worth correcting something I wrote in the original comment: @graphcommerce/magento-customer has no copy/ directory, so these aren't copy-pages — they're scaffolded from the examples.)

The token is on createPassword, not on forgotpassword. Grepping every stock Magento email template for customer URLs:

'customer/account/'                → [_nosid:1]                                   48x
'customer/account/createPassword'  → [_query:[id, token: $customer.rp_token, …]]  12x
'customer/account/confirm/'        → [_query:[id, key: $customer.confirmation, …]] 2x

forgotpassword appears in zero templates. It's the form where a customer types their email address; the reset link Magento mails out points at createPassword with id + rp_token. So the entry wasn't dangerous — it just had no evidence behind it, which is a good enough reason to cut it.

Why the rest are safe, stated properly. The mechanism I should have named up front: a redirects() entry is evaluated before the filesystem routes, so redirecting a path that has a page makes that page unreachable. confirm and createPassword are exactly the two pages that carry a one-shot token, so making either unreachable would break account confirmation and password reset outright. That's why every source is an exact match and why a /customer/account/:path* catch-all is off the table. Re-audited against the inventory above:

source collides with a page?
/customer/account no
/customer/account/index no
/customer/account/login no
/customer/account/create no — exact match, does not cover /customer/account/createPassword
/sales/order/history no

That fourth row is the one that looks alarming at a glance and I suspect is what caught your eye. Next matches source against the full path, so /customer/account/create and /customer/account/createPassword are distinct routes; the redirect fires only on the former. I've made the comment in the code say all of this explicitly instead of hand-waving at "shadowing".

On evidence tiers for what's left, so you can trim further if you'd rather: /customer/account is the strong one (48 email templates). /customer/account/login is backed in-repo — magento-product-downloadable's copy pages already sniff for that string in Magento's 302 Location. /customer/account/index is the same controller action as the first, same destination, zero risk. /customer/account/create and /sales/order/history are the weakest — no direct hit in the mail templates, just canonical Magento entry points people bookmark or land on from an old indexed URL. Happy to drop those two as well if you'd prefer the list to be strictly email-evidenced.

Mirrored downstream in ho-nl/project-jumbosports-gc (b48a202) so the project list stays identical to this one.

Review feedback: the entry had no evidence behind it. Grepping every stock
Magento email template shows only three customer URLs — customer/account/
(48x), customer/account/createPassword (12x, carries id + rp_token) and
customer/account/confirm (2x, carries id + key). forgotpassword appears in
none of them; Magento's reset token rides on createPassword, and
forgotpassword is just the form where a customer types their email.

The remaining five are re-audited for shadowing. GraphCommerce serves exactly
two paths under /customer: confirm and createPassword, both real pages in all
three examples. None of the five sources collide with them (/customer/account/
create is an exact match and does not cover createPassword), and the comment
now states the mechanism that makes this matter: a redirect wins over a
filesystem route, so redirecting a path that has a page makes that page
unreachable.

Also corrects the comment's claim that @graphcommerce/magento-customer copies
those pages in — it has no copy/ directory; they are scaffolded from the
examples.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant