Weaverse LogoWeaverse
Developer Guides

React Router Ecommerce: Modern JavaScript for Shopify

Learn React Router ecommerce development for Shopify Hydrogen stores. Understand modern JavaScript architecture, component-based design, and performance advantages over traditional Liquid themes.

React Router Ecommerce: Modern JavaScript for Shopify

React Router Ecommerce: From Code to Visual Customization

Learn React Router ecommerce development for Shopify Hydrogen stores. Understand modern JavaScript architecture, component-based design, and performance advantages over traditional Liquid themes - plus how to make it all visually customizable.


The React Router Revolution in Ecommerce

React Router v7: Ecommerce Game Changer

React Router v7 brings enterprise-grade routing, server-side rendering, and automatic code splitting to Shopify Hydrogen. For the first time, ecommerce development feels like building a modern web application.

But there's a critical business reality: 99% of merchants can't edit React code.

The Business Challenge: React Router ecommerce is technically superior, but creates a barrier between developers and business users. When your client wants to change a product layout or update promotional content, they can't do it themselves.

The Weaverse Solution: Visual customization layer specifically built for React Router architecture, bridging the gap between technical excellence and business usability.

React Router v7: Technical Excellence

Modern Routing Architecture

React Router v7 transforms how ecommerce applications handle navigation and data loading:

// routes/products.$handle.tsx - Modern ecommerce routing
import type { Route } from './+types/products.$handle'
import { getProduct, getProductRecommendations } from '~/lib/shopify'

export async function loader({ params, request }: Route.LoaderArgs) {
  let { handle } = params
  let url = new URL(request.url)
  let selectedOptions = url.searchParams.getAll('option')

  let [product, recommendations] = await Promise.all([
    getProduct(handle, { selectedOptions }),
    getProductRecommendations(handle, { limit: 4 })
  ])

  if (!product) {
    throw new Response('Product not found', { status: 404 })
  }

  return { product, recommendations, selectedOptions }
}

export const meta: Route.MetaFunction = ({ data }) => {
  if (!data?.product) return [{ title: 'Product Not Found' }]

  let { product } = data
  return [
    { title: `${product.title} | Your Store` },
    { name: 'description', content: product.description },
    { property: 'og:title', content: product.title },
    { property: 'og:image', content: product.featuredImage?.url },
    { property: 'product:price:amount', content: product.priceRange.minVariantPrice.amount }
  ]
}

export default function ProductPage({ loaderData }: Route.ComponentProps) {
  let { product, recommendations } = loaderData

  return (
    <div className="container mx-auto px-4 py-8">
      <ProductDetails product={product} />
      <ProductRecommendations products={recommendations} />
    </div>
  )
}
typescript

Performance Advantages

Initial Page Load

Traditional SPA
4.2s
React Router SSR
0.8s
81% faster

Navigation Speed

Traditional SPA
2.1s (Full page reload)
React Router SSR
0.2s (Client-side routing)
90% faster

Bundle Size

Traditional SPA
680kb (Monolithic)
React Router SSR
120kb (Code splitting)
82% smaller

Core Web Vitals

Traditional SPA
Poor (Red)
React Router SSR
Good (Green)
100% pass rate

The Business Reality: Visual Customization is Essential

Why React Router Alone Isn't Enough

While React Router v7 provides exceptional developer experience and performance, it creates new challenges for business users:

Developer vs Business User Experience

FeatureDeveloper ExperienceBusiness User Experience
Making Content Changes
Edit JSX components, commit to Git, deployNeed immediate visual editing capabilities
Page Layout Updates
Modify component structure and stylingWant drag-and-drop component arrangement
Product Presentation
Build custom components for each layoutNeed multiple display options without coding
Seasonal Campaigns
Create new components, test, deployWant instant promotional banner updates
A/B Testing
Implement feature flags and component variantsNeed visual testing of different layouts

The Weaverse Bridge: Best of Both Worlds

Weaverse solves this by providing a visual layer that preserves all React Router benefits while enabling business user control:

The Complete Weaverse Solution

FeatureDeveloper Benefits PreservedBusiness User Empowerment
Performance & Architecture
Full React Router v7 performance benefits with server-side rendering and code splittingVisual drag-and-drop page building with real-time content editing
Code Quality & Safety
TypeScript type safety maintained throughout the entire stackNo-code component customization without touching code
Development Workflow
Git-based development workflow with component reusability and testingInstant preview and publishing with seasonal campaign management

Building Production-Ready React Router Ecommerce

1. Component Architecture for Visual Editing

The key is building React Router components that work beautifully in code and as visual building blocks:

// CollectionGrid.tsx - Production-ready component
import { forwardRef } from 'react'
import { Image } from '@shopify/hydrogen'
import { createSchema } from '@weaverse/hydrogen'
import type { Collection } from '@shopify/hydrogen/storefront-api-types'

interface CollectionGridProps {
  collections: Collection[]
  columns?: 2 | 3 | 4
  showDescription?: boolean
  showProductCount?: boolean
  aspectRatio?: 'square' | 'portrait' | 'landscape'
  layoutStyle?: 'card' | 'overlay' | 'minimal'
}

export let CollectionGrid = forwardRef<HTMLDivElement, CollectionGridProps>(
  ({
    collections,
    columns = 3,
    showDescription = true,
    showProductCount = false,
    aspectRatio = 'square',
    layoutStyle = 'card'
  }, ref) => {
    return (
      <div
        ref={ref}
        className={`grid gap-6 grid-cols-1 md:grid-cols-${columns}`}
      >
        {collections.map((collection) => (
          <div
            key={collection.id}
            className={`group cursor-pointer ${
              layoutStyle === 'card' ? 'bg-white rounded-lg shadow-md overflow-hidden' :
              layoutStyle === 'overlay' ? 'relative' :
              'border-b border-gray-200 pb-4'
            }`}
          >
            <div className="relative overflow-hidden">
              <Image
                data={collection.image}
                aspectRatio={aspectRatio}
                className="w-full h-full object-cover transition-transform group-hover:scale-105"
              />
              {layoutStyle === 'overlay' && (
                <div className="absolute inset-0 bg-black bg-opacity-40 flex items-center justify-center">
                  <h3 className="text-white text-2xl font-bold text-center">
                    {collection.title}
                  </h3>
                </div>
              )}
            </div>

            {layoutStyle !== 'overlay' && (
              <div className="p-4">
                <h3 className="text-xl font-semibold mb-2">{collection.title}</h3>
                {showDescription && collection.description && (
                  <p className="text-gray-600 mb-2">{collection.description}</p>
                )}
                {showProductCount && (
                  <p className="text-sm text-gray-500">
                    {collection.products.edges.length} products
                  </p>
                )}
              </div>
            )}
          </div>
        ))}
      </div>
    )
  }
)

// Weaverse schema for visual editing
export let schema = createSchema({
  type: 'collection-grid',
  title: 'Collection Grid',
  settings: [
    {
      group: 'Layout',
      inputs: [
        {
          type: 'range',
          name: 'columns',
          label: 'Columns',
          min: 2,
          max: 4,
          step: 1,
          defaultValue: 3,
        },
        {
          type: 'select',
          name: 'aspectRatio',
          label: 'Image Aspect Ratio',
          options: [
            { value: 'square', label: 'Square (1:1)' },
            { value: 'portrait', label: 'Portrait (3:4)' },
            { value: 'landscape', label: 'Landscape (4:3)' },
          ],
          defaultValue: 'square',
        },
        {
          type: 'select',
          name: 'layoutStyle',
          label: 'Layout Style',
          options: [
            { value: 'card', label: 'Card Style' },
            { value: 'overlay', label: 'Text Overlay' },
            { value: 'minimal', label: 'Minimal' },
          ],
          defaultValue: 'card',
        },
      ],
    },
    {
      group: 'Content Display',
      inputs: [
        {
          type: 'toggle',
          name: 'showDescription',
          label: 'Show Description',
          defaultValue: true,
        },
        {
          type: 'toggle',
          name: 'showProductCount',
          label: 'Show Product Count',
          defaultValue: false,
        },
      ],
    },
    {
      group: 'Collections',
      inputs: [
        {
          type: 'collection',
          name: 'collections',
          label: 'Select Collections',
          multiple: true,
        },
      ],
    },
  ],
})
typescript

2. Advanced Routing Patterns

React Router v7 enables sophisticated ecommerce routing patterns:

// routes/search.tsx - Advanced search routing
import type { Route } from './+types/search'
import { searchProducts, getFilters } from '~/lib/shopify'

export async function loader({ request }: Route.LoaderArgs) {
  let url = new URL(request.url)
  let query = url.searchParams.get('q') || ''
  let sortBy = url.searchParams.get('sort') || 'relevance'
  let filters = url.searchParams.getAll('filter')
  let page = Number(url.searchParams.get('page')) || 1

  let [searchResults, availableFilters] = await Promise.all([
    searchProducts({
      query,
      sortBy,
      filters,
      page,
      pageSize: 24
    }),
    getFilters(query)
  ])

  return {
    searchResults,
    availableFilters,
    currentQuery: query,
    currentSort: sortBy,
    currentFilters: filters,
    currentPage: page
  }
}

export default function SearchPage({ loaderData }: Route.ComponentProps) {
  let { searchResults, availableFilters, currentQuery } = loaderData

  return (
    <div className="container mx-auto px-4 py-8">
      <SearchFilters filters={availableFilters} />
      <SearchResults
        products={searchResults.products}
        totalCount={searchResults.totalCount}
        query={currentQuery}
      />
      <SearchPagination
        currentPage={searchResults.currentPage}
        totalPages={searchResults.totalPages}
      />
    </div>
  )
}
typescript

3. State Management in Visual Components

// CartDrawer.tsx - Stateful component with visual settings
import { useState, useEffect } from 'react'
import { createSchema } from '@weaverse/hydrogen'
import { useCart } from '~/hooks/useCart'

interface CartDrawerProps {
  slideDirection?: 'left' | 'right'
  showContinueShopping?: boolean
  showOrderNotes?: boolean
  enableUpsells?: boolean
  freeShippingThreshold?: number
}

export function CartDrawer({
  slideDirection = 'right',
  showContinueShopping = true,
  showOrderNotes = false,
  enableUpsells = false,
  freeShippingThreshold = 0
}: CartDrawerProps) {
  let { isOpen, setIsOpen, items, totalPrice } = useCart()
  let [orderNotes, setOrderNotes] = useState('')

  let freeShippingRemaining = freeShippingThreshold > 0
    ? Math.max(0, freeShippingThreshold - totalPrice)
    : 0

  return (
    <div
      className={`fixed inset-y-0 ${slideDirection}-0 z-50 w-96 bg-white shadow-xl transform transition-transform ${
        isOpen ? 'translate-x-0' : slideDirection === 'right' ? 'translate-x-full' : '-translate-x-full'
      }`}
    >
      <div className="flex flex-col h-full">
        <header className="flex items-center justify-between p-4 border-b">
          <h2 className="text-lg font-semibold">Shopping Cart ({items.length})</h2>
          <button onClick={() => setIsOpen(false)}>×</button>
        </header>

        {freeShippingThreshold > 0 && (
          <div className="p-4 bg-blue-50 border-b">
            {freeShippingRemaining > 0 ? (
              <p className="text-sm text-blue-800">
                Add ${freeShippingRemaining.toFixed(2)} more for free shipping!
              </p>
            ) : (
              <p className="text-sm text-green-800">
                🎉 You qualify for free shipping!
              </p>
            )}
          </div>
        )}

        <div className="flex-1 overflow-y-auto p-4">
          {items.map((item) => (
            <CartItem key={item.id} item={item} />
          ))}

          {enableUpsells && items.length > 0 && (
            <UpsellRecommendations cartItems={items} />
          )}
        </div>

        {showOrderNotes && (
          <div className="p-4 border-t">
            <label className="block text-sm font-medium mb-2">
              Order Notes
            </label>
            <textarea
              value={orderNotes}
              onChange={(e) => setOrderNotes(e.target.value)}
              className="w-full p-2 border rounded"
              rows={3}
              placeholder="Special instructions..."
            />
          </div>
        )}

        <footer className="p-4 border-t">
          <div className="flex justify-between items-center mb-4">
            <span className="text-lg font-semibold">Total:</span>
            <span className="text-lg font-semibold">${totalPrice.toFixed(2)}</span>
          </div>

          <button className="w-full bg-black text-white py-3 rounded mb-2">
            Checkout
          </button>

          {showContinueShopping && (
            <button
              onClick={() => setIsOpen(false)}
              className="w-full border border-gray-300 py-2 rounded"
            >
              Continue Shopping
            </button>
          )}
        </footer>
      </div>
    </div>
  )
}

export let schema = createSchema({
  type: 'cart-drawer',
  title: 'Shopping Cart Settings',
  settings: [
    {
      group: 'Appearance',
      inputs: [
        {
          type: 'select',
          name: 'slideDirection',
          label: 'Slide Direction',
          options: [
            { value: 'left', label: 'Slide from Left' },
            { value: 'right', label: 'Slide from Right' },
          ],
          defaultValue: 'right',
        },
      ],
    },
    {
      group: 'Features',
      inputs: [
        {
          type: 'toggle',
          name: 'showContinueShopping',
          label: 'Show Continue Shopping Button',
          defaultValue: true,
        },
        {
          type: 'toggle',
          name: 'showOrderNotes',
          label: 'Enable Order Notes',
          defaultValue: false,
        },
        {
          type: 'toggle',
          name: 'enableUpsells',
          label: 'Show Upsell Recommendations',
          defaultValue: false,
        },
        {
          type: 'number',
          name: 'freeShippingThreshold',
          label: 'Free Shipping Threshold ($)',
          min: 0,
          step: 1,
          defaultValue: 0,
          placeholder: '0 = disabled',
        },
      ],
    },
  ],
})
typescript

Case Study: Agency Success with React Router + Weaverse

The Challenge: High-End Fashion Brand

Client Requirements:

  • Luxury brand aesthetic with high-performance site
  • Seasonal campaign management without developer involvement
  • A/B testing capabilities for product layouts
  • Mobile-first design with perfect performance scores

Solution Architecture:

  • React Router v7 for performance and SEO
  • Custom React components for brand-specific experiences
  • Weaverse visual layer for business user control
  • Advanced state management for personalization

Implementation Results

Campaign Launch Time

Before Implementation
2-3 weeks
After Implementation
1 day
95% faster

Lighthouse Performance Score

Before Implementation
67
After Implementation
98
+31 points

Conversion Rate

Before Implementation
2.8%
After Implementation
4.1%
+46% increase

Developer Hours/Month

Before Implementation
80 hours
After Implementation
12 hours
85% reduction

Client Satisfaction

Before Implementation
7/10
After Implementation
9.8/10
+40% increase

Client Testimonial

"The combination of React Router performance with Weaverse's visual control has transformed our business. We can launch seasonal campaigns instantly, test different layouts in real-time, and maintain perfect Lighthouse scores. Our conversion rates have never been higher."

— Marcus Rodriguez, Head of Digital at Luxe Fashion Co.

Why Traditional Page Builders Can't Compete

Weaverse vs Traditional Page Builders for React Router

FeatureWeaverse (React Router Native)Traditional Builders (Liquid Only)
Framework Compatibility
Built specifically for React Router v7Limited to legacy Liquid templates
Performance Impact
Zero performance loss, maintains SSR + code splittingAdds overhead, breaks performance optimizations
Component Integration
Edit actual React components visuallyCannot work with React component architecture
TypeScript Support
Full type safety in visual editorNo TypeScript integration
Development Workflow
Git-compatible, version controlledSeparate system, workflow conflicts
Future Compatibility
Evolves with React Router and HydrogenStuck with legacy template limitations

Getting Started with React Router + Weaverse

Quick Start Guide

# 1. Create new Hydrogen project with Weaverse
npx @weaverse/cli create my-ecommerce-store
cd my-ecommerce-store

# 2. Install dependencies and start development
npm install
npm run dev

# 3. Access Weaverse visual editor
# Visit: http://localhost:3000/weaverse
bash

First Component Migration

Convert your first React Router component to be visually editable:

// 1. Add Weaverse schema to existing component
import { createSchema } from '@weaverse/hydrogen'

// 2. Add forwardRef for visual editor compatibility
export let YourComponent = forwardRef<HTMLDivElement, YourProps>(
  (props, ref) => {
    return <div ref={ref}>{/* your component */}</div>
  }
)

// 3. Define visual editing schema
export let schema = createSchema({
  type: 'your-component',
  title: 'Your Component',
  settings: [/* your settings */]
})
typescript

Performance Optimization

Maintain React Router performance benefits:

// Lazy load Weaverse components for optimal performance
import { lazy } from 'react'

let ProductGrid = lazy(() => import('~/components/ProductGrid'))
let CollectionBanner = lazy(() => import('~/components/CollectionBanner'))

// Components maintain SSR and code splitting benefits
typescript

The Future of React Router Ecommerce

React Router + Weaverse: The Complete Solution

React Router v7 provides the technical foundation for modern ecommerce, but Weaverse completes the story by adding essential visual customization. Together, they create the perfect balance of developer productivity and business user empowerment.

For Developers:

  • Build components once, customize infinitely
  • Maintain React Router performance benefits
  • Focus on features, not repetitive customization
  • TypeScript safety throughout

For Business Users:

  • Visual control over React components
  • Instant campaign launches
  • A/B testing capabilities
  • No waiting for developer changes

For Agencies:

  • 10x faster project delivery
  • Higher client satisfaction
  • Reduced support overhead
  • Premium pricing justified

Ready to Build the Future of Ecommerce?

Stop choosing between technical excellence and business usability. Get both with React Router v7 + Weaverse - the only complete solution for modern Shopify development.

Start Building with React Router + Weaverse →


Learn more about TypeScript Shopify Development or discover Git-Based Theme Development workflows.

Never miss an update

Subscribe to get the latest insights, tutorials, and best practices for building high-performance headless stores delivered to your inbox.

Join the community of developers building with Weaverse.