Skip to main content

Custom Routing with Weaverse

This guide explains how to implement custom URL structures in your Shopify Hydrogen storefront using Weaverse, allowing you to move beyond the standard /products/:handle, /collections/:handle, or /pages/:handle paths provided by default.

Overview

While Hydrogen and React Router 7 provide powerful routing capabilities, you might want URLs that are more SEO-friendly, aligned with marketing campaigns, localized, or simply structured differently from Shopify’s defaults. Examples include:
  • /sale/:campaignName (for a marketing landing page)
  • /fr/produits/:productHandle (for a localized product page)
  • /lookbooks/:collectionHandle (for a themed collection page)
  • /guides/:pageHandle (for content pages using Weaverse’s “Custom Page” feature)
Weaverse enables this by decoupling the URL structure from the content rendering mechanism.

Core Concept: loadPage Drives Content

The key principle is that the content Weaverse renders is determined by the parameters you pass to the context.weaverse.loadPage() function within your route module’s loader, specifically the type and handle arguments.
  • type: Tells Weaverse what kind of content to load (e.g., 'PRODUCT', 'COLLECTION', 'PAGE', 'CUSTOM'). See Rendering Weaverse Pages for a full list of types.
  • handle: Tells Weaverse which specific instance of that content type to load (e.g., the handle of a specific product or collection). CUSTOM pages are resolved from the request URL, so they need no handle.
Custom routing, therefore, involves:
  1. Creating a route module for your desired URL pattern.
  2. Registering that module in app/routes.ts — Pilot uses React Router 7’s manual route registry, so creating a file under app/routes/ does not register a route by itself.
  3. Implementing a loader in that module and extracting the identifier (like a handle) from the URL parameters.
  4. Calling context.weaverse.loadPage() with the explicit type corresponding to the content you want to display, plus the extracted handle when the type needs one.
  5. Declaring the matching theme routes so Studio can navigate to those URLs from its page selector and URL pickers.
Weaverse then fetches the appropriate page data based on these parameters, regardless of the actual URL structure.
Route modules are ordinary React Router 7 modules: import LoaderFunctionArgs, MetaFunction, and useLoaderData from react-router (not from @remix-run/react or @shopify/remix-oxygen), and return plain objects from loaders instead of json().

Relationship to Custom Pages & Templates

  • Custom Pages: Custom Routing is often used to provide user-friendly URLs for Custom Pages built in Weaverse. Load those with loadPage({ type: 'CUSTOM' }) — Weaverse resolves the page from the request URL. Reserve type: 'PAGE' with a handle for Shopify Page resources, the way Pilot’s app/routes/pages/regular-page.tsx serves /pages/:pageHandle.
  • Custom Templates: Custom Routing works seamlessly with Custom Templates. If a resource (like a product) fetched via a custom route has a custom template assigned, loadPage automatically uses it. You don’t need extra logic in your custom route’s loader for this.

Declare Theme Routes for Studio

When your storefront uses different paths from Shopify’s defaults, add routes to the theme schema you pass to Weaverse. Studio uses this map to build paths for its page selector and URL pickers. Declare only the page types whose paths differ; omitted types keep the Shopify convention. routes is a sibling of settings, not a merchant-editable input. For the /shop/:productHandle route in the implementation below, add this declaration to your existing theme schema:
Use :handle in the schema even when your router calls the parameter :productHandle. Studio fills its own named parameters; it does not read your router’s parameter names. Keep the rest of your existing theme schema, including any localization configuration.
The shared ThemeRoutes type is available in @weaverse/schema 0.18.0+. For typed routes declarations on the theme schema, use @weaverse/hydrogen 5.23.0+ or the stable @weaverse/next 1.0.0+. These versions describe SDK type support; Studio resolves the paths from the schema it receives in design mode.

Example: Forward

Forward’s theme schema uses the same map on WeaverseNextThemeSchema. Its routes field contains:
For example, Studio opens collection summer at /shop/summer and article hello from blog news at /journal/hello. Forward’s article route does not include the blog segment. A theme that needs both segments can instead declare ARTICLE: '/blogs/:blog/:handle'.

Supported Keys and Patterns

The supported keys are INDEX, PRODUCT, ALL_PRODUCTS, COLLECTION, COLLECTION_LIST, PAGE, BLOG, and ARTICLE. CUSTOM pages already carry their own path, and * is not a route key. See the shared route types. Use paths beginning with /, containing static segments and these whole-segment parameters: Static paths such as ALL_PRODUCTS: '/shop' need no parameters. Optional segments, wildcards, and embedded parameters such as /shop-:handle are unsupported. Use the fixed names above, not :productHandle or :article. Keep patterns relative to the locale root: declare /shop/:handle, not /fr/shop/:handle or /:locale/shop/:handle. The Studio page selector adds the selected locale’s configured path prefix separately.

Fallback and Scope

  • No map or missing key: Studio uses the Shopify path for that page type. For example, declaring only COLLECTION leaves products at /products/:handle and articles at /blogs/:blog/:handle.
  • Cannot fill the pattern: A path without a leading /, or a whole-segment parameter with no available value, falls back to the Shopify convention. Unsupported syntax is not comprehensively validated; keep to the grammar above.
  • The storefront must serve the URL: Declaring routes does not register routes, create redirects, or rewrite links in your theme. A filled pattern can still lead to a 404 if your storefront does not serve it. Keep your route modules, navigation links, and sitemaps aligned with the map.
  • Studio scope: The map applies where Studio has loaded the theme schema. Links that open Studio from the dashboard still use the Shopify convention; this field does not change that entry URL.

Implementation Steps

Let’s walk through creating a custom route to display a product page at /shop/:productHandle instead of the standard /products/:productHandle.

Step 1: Create the Route Module

Create a new module in your Hydrogen project at app/routes/items/item.tsx. The file path is just a module location — the URL comes from the registration in the next step.

Step 2: Register the Route in app/routes.ts

Pilot defines its routes explicitly with React Router 7’s route helpers, so a new file under app/routes/ is not reachable until you add it to the registry. Add your pattern inside the :locale? prefix block so the route inherits the storefront’s locale prefix:
  • :locale? — the optional locale prefix Pilot already applies to storefront routes.
  • shop/:productHandle — your custom URL pattern, replacing products/:productHandle.
  • The second argument is the module path relative to app/.
React Router ranks a concrete pattern like shop/:productHandle above the * catch-all, so placement inside the block does not matter — but keep it inside the :locale? prefix, otherwise /fr/shop/... will fall through to the catch-all.

Step 3: Implement the Loader Function

Key Points in the Loader:
  • Explicit type: "PRODUCT": This is critical. It tells Weaverse to load the content configured for a product page, even though the URL is /shop/....
  • Extract productHandle: Get the handle from params.
  • Fetch Shopify Data: You still need to fetch the corresponding Shopify product data for SEO, potentially for component data binding (though Weaverse handles much of this), and analytics. Use Promise.all for efficiency.
  • Nullable Weaverse data: loadPage() never rejects on a load failure — it logs and resolves to null. A try/catch around it catches nothing, so branch on the result (or hand it to validateWeaverseData). Keep try/catch for the operations that really do throw, such as storefront.query.
If your theme renders a fallback layout when Weaverse has no page for the resource, replace validateWeaverseData(weaverseData) with your own branch instead of throwing — but do check for null. Passing a missing page straight to <WeaverseContent /> renders the error component.

Step 4: Render the Weaverse Content

The component itself remains simple, primarily rendering <WeaverseContent /> which uses the weaverseData fetched in the loader.
Now, visiting /shop/your-product-handle will render the Weaverse product page associated with your-product-handle.

More Examples

Localized Collection Route

  • Desired URL: /fr/collections-en-vedette/:collectionHandle
  • Route Module: app/routes/collections/featured-collection.tsx
  • Registration: route("collections-en-vedette/:collectionHandle", "routes/collections/featured-collection.tsx") inside the :locale? block
  • Loader loadPage Call: weaverse.loadPage({ type: 'COLLECTION', handle: params.collectionHandle })
  • Loader Shopify Data Fetch: Query for the specific collection data using params.collectionHandle.

Marketing Campaign Page (using Weaverse Custom Page)

  • Assumption: You built a “Custom Page” in Weaverse Studio with the handle summer-sale-2025.
  • Desired URL: /summer-sale-2025
  • Route Module: already covered by Pilot’s catch-all, app/routes/catch-all.tsx, registered as route("*", "routes/catch-all.tsx")
  • Loader loadPage Call: weaverse.loadPage({ type: 'CUSTOM' }) — the handle comes from the request URL, so no handle argument
  • Loader Shopify Data Fetch: Likely none needed unless the page displays specific dynamic products/collections queried separately.
Root-level custom pages need no new route at all: Pilot’s catch-all already resolves any unmatched URL as a CUSTOM page. Add a dedicated route only when you want a different URL shape (e.g. nesting every campaign under /promo/...), and load it with type: 'CUSTOM' too.

Custom Routing with Localization

Custom routes can be combined with Weaverse’s localization features to support multi-locale storefronts with custom URL structures.

Explicit Locale in Custom Routes

When implementing custom routes, you can explicitly pass the locale parameter to loadPage() to ensure the correct localized content is loaded:

Localized Custom URL Patterns

Create locale-specific custom routes with different URL structures per market:

Dynamic Locale Selection in Custom Routes

Combine custom routing with dynamic locale detection:

Multi-Project Custom Routing

For top-level domain or subdomain-based localization, combine custom routing with multi-project architecture:
Benefits:
  • Completely different URL structures per market (e.g., /vara/ for Swedish, /shop/ for English)
  • Independent theme settings and page structures per project
  • Support for top-level domains (mystore.se, mystore.fr) without complex routing
See the Multi-Project Architecture Guide for comprehensive documentation on multi-project routing, including advanced routing patterns, A/B testing, and multi-brand storefronts.

Important Considerations

  • Register Every Route: A module under app/routes/ is unreachable until it appears in app/routes.ts. If a custom URL 404s or renders your catch-all page, check the registry first.
  • Choose the Correct type: Always ensure the type parameter in loadPage matches the kind of content you intend to display (PRODUCT, COLLECTION, PAGE, BLOG, ARTICLE, CUSTOM, etc.). Use CUSTOM for Weaverse custom pages and PAGE + handle for Shopify Pages. Refer to the Supported Page Types.
  • Fetch Necessary Shopify Data: If your custom route replaces a standard route (like product or collection), remember to fetch the corresponding Shopify data in your loader for SEO, analytics, or any components that might need it directly.
  • Generating Links: Linking to these custom routes from within your application (e.g., in navigation or product grids) will require using the correct path (/shop/..., /fr/collections-en-vedette/...) instead of the default Shopify paths. You might need custom link-building logic or helper functions.
  • SEO & Sitemaps: Standard sitemap generation might not automatically include these custom routes. You may need to manually add them to your sitemap.xml logic. Ensure canonical URLs are correctly set (perhaps pointing to the standard Shopify URL if preferred, or self-referencing the custom URL if it’s the primary one). Consult SEO best practices.

This approach provides significant flexibility in structuring your Hydrogen storefront’s URLs while leveraging Weaverse for visual page building and content management.
Last modified on October 13, 2026